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 }