github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/integration/messagebus/glue/backend/server.go (about)

     1  /*
     2   *  Glue - Robust Go and Javascript Socket Library
     3   *  Copyright (C) 2015  Roland Singer <roland.singer[at]desertbit.com>
     4   *
     5   *  This program is free software: you can redistribute it and/or modify
     6   *  it under the terms of the GNU General Public License as published by
     7   *  the Free Software Foundation, either version 3 of the License, or
     8   *  (at your option) any later version.
     9   *
    10   *  This program is distributed in the hope that it will be useful,
    11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   *  GNU General Public License for more details.
    14   *
    15   *  You should have received a copy of the GNU General Public License
    16   *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   */
    18  
    19  // Package backend provides the server backend with various socket implementations.
    20  package backend
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  
    26  	"github.com/mdaxf/iac/integration/messagebus/glue/backend/sockets/ajaxsocket"
    27  	"github.com/mdaxf/iac/integration/messagebus/glue/backend/sockets/websocket"
    28  	"github.com/mdaxf/iac/integration/messagebus/glue/log"
    29  	"github.com/mdaxf/iac/integration/messagebus/glue/utils"
    30  	"github.com/sirupsen/logrus"
    31  )
    32  
    33  //#################//
    34  //### Constants ###//
    35  //#################//
    36  
    37  const (
    38  	httpURLAjaxSocketSuffix = "ajax"
    39  	httpURLWebSocketSuffix  = "ws"
    40  )
    41  
    42  //######################//
    43  //### Backend Server ###//
    44  //######################//
    45  
    46  type Server struct {
    47  	onNewSocketConnection func(BackendSocket)
    48  
    49  	// An Integer holding the length of characters which should be stripped
    50  	// from the ServerHTTP URL path.
    51  	httpURLStripLength int
    52  
    53  	// checkOriginFunc returns true if the request Origin header is acceptable.
    54  	checkOriginFunc func(r *http.Request) bool
    55  
    56  	// Enables the Cross-Origin Resource Sharing (CORS) mechanism.
    57  	enableCORS bool
    58  
    59  	// Socket Servers
    60  	webSocketServer  *websocket.Server
    61  	ajaxSocketServer *ajaxsocket.Server
    62  }
    63  
    64  func NewServer(httpURLStripLength int, enableCORS bool, checkOrigin func(r *http.Request) bool) *Server {
    65  	// Create a new backend server. func(r *http.Request) bool
    66  	s := &Server{
    67  		// Set a dummy function.
    68  		// This prevents panics, if new sockets are created,
    69  		// but no function was set.
    70  		onNewSocketConnection: func(BackendSocket) {},
    71  
    72  		httpURLStripLength: httpURLStripLength,
    73  		enableCORS:         enableCORS,
    74  		checkOriginFunc:    checkOrigin,
    75  	}
    76  
    77  	// Create the websocket server and pass the function which handles new incoming socket connections.
    78  	s.webSocketServer = websocket.NewServer(func(ws *websocket.Socket) {
    79  		s.triggerOnNewSocketConnection(ws)
    80  	})
    81  
    82  	// Create the ajax server and pass the function which handles new incoming socket connections.
    83  	s.ajaxSocketServer = ajaxsocket.NewServer(func(as *ajaxsocket.Socket) {
    84  		s.triggerOnNewSocketConnection(as)
    85  	})
    86  
    87  	return s
    88  }
    89  
    90  // OnNewSocketConnection sets the event function which is
    91  // triggered if a new socket connection was made.
    92  func (s *Server) OnNewSocketConnection(f func(BackendSocket)) {
    93  	s.onNewSocketConnection = f
    94  }
    95  
    96  // ServeHTTP implements the HTTP Handler interface of the http package.
    97  func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    98  	// Get the URL path.
    99  	path := r.URL.Path
   100  
   101  	// Call this in an inline function to handle errors.
   102  	statusCode, err := func() (int, error) {
   103  		// Check the origin.
   104  		if !s.checkOriginFunc(r) {
   105  			return http.StatusForbidden, fmt.Errorf("origin not allowed")
   106  		}
   107  
   108  		// Set the required HTTP headers for cross origin requests if enabled.
   109  		if s.enableCORS {
   110  			// Parse the origin url.
   111  			origin := r.Header["Origin"]
   112  			if len(origin) == 0 || len(origin[0]) == 0 {
   113  				return 400, fmt.Errorf("failed to set header: Access-Control-Allow-Origin: HTTP request origin header is empty")
   114  			}
   115  
   116  			w.Header().Set("Access-Control-Allow-Origin", origin[0])   // Set allowed origin.
   117  			w.Header().Set("Access-Control-Allow-Methods", "POST,GET") // Only allow POST and GET requests.
   118  		}
   119  
   120  		// Strip the base URL.
   121  		if len(path) < s.httpURLStripLength {
   122  			return http.StatusBadRequest, fmt.Errorf("invalid request")
   123  		}
   124  		path = path[s.httpURLStripLength:]
   125  
   126  		// Route the HTTP request in a very simple way by comparing the strings.
   127  		if path == httpURLWebSocketSuffix {
   128  			// Handle the websocket request.
   129  			s.webSocketServer.HandleRequest(w, r)
   130  		} else if path == httpURLAjaxSocketSuffix {
   131  			// Handle the ajax request.
   132  			s.ajaxSocketServer.HandleRequest(w, r)
   133  		} else {
   134  			return http.StatusBadRequest, fmt.Errorf("invalid request")
   135  		}
   136  
   137  		return http.StatusAccepted, nil
   138  	}()
   139  
   140  	// Handle the error.
   141  	if err != nil {
   142  		// Set the HTTP status code.
   143  		w.WriteHeader(statusCode)
   144  
   145  		// Get the remote address and user agent.
   146  		remoteAddr, _ := utils.RemoteAddress(r)
   147  		userAgent := r.Header.Get("User-Agent")
   148  
   149  		// Log the invalid request.
   150  		log.L.WithFields(logrus.Fields{
   151  			"remoteAddress": remoteAddr,
   152  			"userAgent":     userAgent,
   153  			"url":           r.URL.Path,
   154  		}).Warningf("handle HTTP request: %v", err)
   155  	}
   156  }
   157  
   158  //################################//
   159  //### Backend Server - Private ###//
   160  //################################//
   161  
   162  func (s *Server) triggerOnNewSocketConnection(bs BackendSocket) {
   163  	// Trigger the on new socket connection event in a new goroutine
   164  	// to not block any socket functions. Otherwise this might block HTTP handlers.
   165  	go s.onNewSocketConnection(bs)
   166  }