github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/integration/messagebus/glue/backend/sockets/ajaxsocket/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 ajaxsocket provides the ajax socket implementation. 20 package ajaxsocket 21 22 import ( 23 "io" 24 "io/ioutil" 25 "net/http" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/mdaxf/iac/integration/messagebus/glue/log" 31 "github.com/mdaxf/iac/integration/messagebus/glue/utils" 32 "github.com/sirupsen/logrus" 33 ) 34 35 //#################// 36 //### Constants ###// 37 //#################// 38 39 const ( 40 ajaxPollTimeout = 35 * time.Second 41 ajaxUIDLength = 10 42 ajaxPollTokenLength = 7 43 44 // Ajax poll data commands: 45 ajaxPollCmdTimeout = "t" 46 ajaxPollCmdClosed = "c" 47 48 // Ajax protocol commands: 49 ajaxSocketDataDelimiter = "&" 50 ajaxSocketDataKeyLength = 1 51 ajaxSocketDataKeyInit = "i" 52 ajaxSocketDataKeyPush = "u" 53 ajaxSocketDataKeyPoll = "o" 54 ) 55 56 //########################// 57 //### Ajax Server type ###// 58 //##################äääää#// 59 60 type Server struct { 61 sockets map[string]*Socket 62 socketsMutex sync.Mutex 63 64 onNewSocketConnection func(*Socket) 65 } 66 67 func NewServer(onNewSocketConnectionFunc func(*Socket)) *Server { 68 return &Server{ 69 sockets: make(map[string]*Socket), 70 onNewSocketConnection: onNewSocketConnectionFunc, 71 } 72 } 73 74 func (s *Server) HandleRequest(w http.ResponseWriter, req *http.Request) { 75 // Get the remote address and user agent. 76 remoteAddr, _ := utils.RemoteAddress(req) 77 userAgent := req.Header.Get("User-Agent") 78 79 // Get the request body data. 80 body, err := ioutil.ReadAll(req.Body) 81 if err != nil { 82 log.L.WithFields(logrus.Fields{ 83 "remoteAddress": remoteAddr, 84 "userAgent": userAgent, 85 }).Warningf("failed to read ajax request body: %v", err) 86 87 http.Error(w, "Internal Server Error", 500) 88 return 89 } 90 91 // Check for bad requests. 92 if req.Method != "POST" { 93 log.L.WithFields(logrus.Fields{ 94 "remoteAddress": remoteAddr, 95 "userAgent": userAgent, 96 }).Warningf("client accessed the ajax interface with an invalid http method: %s", req.Method) 97 98 http.Error(w, "Bad Request", 400) 99 return 100 } 101 102 // Get the request body as string. 103 data := string(body) 104 105 // Get the head of the body data delimited by an delimiter. 106 var head string 107 i := strings.Index(data, ajaxSocketDataDelimiter) 108 if i < 0 { 109 // There is no delimiter. The complete data is the head. 110 head = data 111 data = "" 112 } else { 113 // Extract the head. 114 head = data[:i] 115 data = data[i+1:] 116 } 117 118 // Validate the head length. 119 if len(head) < ajaxSocketDataKeyLength { 120 log.L.WithFields(logrus.Fields{ 121 "remoteAddress": remoteAddr, 122 "userAgent": userAgent, 123 }).Warningf("ajax: head data is too short: '%s'", head) 124 125 http.Error(w, "Bad Request", 400) 126 return 127 } 128 129 // The head is split into key and value. 130 key := head[:ajaxSocketDataKeyLength] 131 value := head[ajaxSocketDataKeyLength:] 132 133 // Handle the specific request. 134 switch key { 135 case ajaxSocketDataKeyInit: 136 s.initAjaxRequest(remoteAddr, userAgent, w) 137 case ajaxSocketDataKeyPoll: 138 s.pollAjaxRequest(value, remoteAddr, userAgent, data, w) 139 case ajaxSocketDataKeyPush: 140 s.pushAjaxRequest(value, remoteAddr, userAgent, data, w) 141 default: 142 log.L.WithFields(logrus.Fields{ 143 "remoteAddress": remoteAddr, 144 "userAgent": userAgent, 145 "key": key, 146 "value": value, 147 }).Warningf("ajax: invalid request.") 148 149 http.Error(w, "Bad Request", 400) 150 return 151 } 152 } 153 154 func (s *Server) initAjaxRequest(remoteAddr, userAgent string, w http.ResponseWriter) { 155 var uid string 156 157 // Create a new ajax socket value. 158 a := newSocket(s) 159 a.remoteAddr = remoteAddr 160 a.userAgent = userAgent 161 162 func() { 163 // Lock the mutex 164 s.socketsMutex.Lock() 165 defer s.socketsMutex.Unlock() 166 167 // Obtain a new unique ID. 168 for { 169 // Generate it. 170 uid = utils.RandomString(ajaxUIDLength) 171 172 // Check if the new UID is already used. 173 // This is very unlikely, but we have to check this! 174 _, ok := s.sockets[uid] 175 if !ok { 176 // Break the loop if the UID is unique. 177 break 178 } 179 } 180 181 // Set the UID. 182 a.uid = uid 183 184 // Add the new ajax socket to the map. 185 s.sockets[uid] = a 186 }() 187 188 // Create a new poll token. 189 a.pollToken = utils.RandomString(ajaxPollTokenLength) 190 191 // Tell the client the UID and poll token. 192 io.WriteString(w, uid+ajaxSocketDataDelimiter+a.pollToken) 193 194 // Trigger the event that a new socket connection was made. 195 s.onNewSocketConnection(a) 196 } 197 198 func (s *Server) pushAjaxRequest(uid, remoteAddr, userAgent, data string, w http.ResponseWriter) { 199 // Obtain the ajax socket with the uid. 200 a := func() *Socket { 201 // Lock the mutex. 202 s.socketsMutex.Lock() 203 defer s.socketsMutex.Unlock() 204 205 // Obtain the ajax socket with the uid- 206 a, ok := s.sockets[uid] 207 if !ok { 208 return nil 209 } 210 return a 211 }() 212 213 if a == nil { 214 log.L.WithFields(logrus.Fields{ 215 "remoteAddress": remoteAddr, 216 "userAgent": userAgent, 217 "uid": uid, 218 }).Warningf("ajax: client requested an invalid ajax socket: uid is invalid!") 219 220 http.Error(w, "Bad Request", 400) 221 return 222 } 223 224 // The user agents have to match. 225 if a.userAgent != userAgent { 226 log.L.WithFields(logrus.Fields{ 227 "remoteAddress": remoteAddr, 228 "userAgent": userAgent, 229 "uid": uid, 230 "clientUserAgent": userAgent, 231 "socketUserAgent": a.userAgent, 232 }).Warningf("ajax: client push request: user agents do not match!") 233 234 http.Error(w, "Bad Request", 400) 235 return 236 } 237 238 // Check if the push request was called with no data. 239 if len(data) == 0 { 240 log.L.WithFields(logrus.Fields{ 241 "remoteAddress": remoteAddr, 242 "userAgent": userAgent, 243 "uid": uid, 244 }).Warningf("ajax: client push request with no data!") 245 246 http.Error(w, "Bad Request", 400) 247 return 248 } 249 250 // Update the remote address. The client might be behind a proxy. 251 a.remoteAddr = remoteAddr 252 253 // Write the received data to the read channel. 254 a.readChan <- data 255 } 256 257 func (s *Server) pollAjaxRequest(uid, remoteAddr, userAgent, data string, w http.ResponseWriter) { 258 // Obtain the ajax socket with the uid. 259 a := func() *Socket { 260 // Lock the mutex. 261 s.socketsMutex.Lock() 262 defer s.socketsMutex.Unlock() 263 264 // Obtain the ajax socket with the uid- 265 a, ok := s.sockets[uid] 266 if !ok { 267 return nil 268 } 269 return a 270 }() 271 272 if a == nil { 273 log.L.WithFields(logrus.Fields{ 274 "remoteAddress": remoteAddr, 275 "userAgent": userAgent, 276 "uid": uid, 277 }).Warningf("ajax: client requested an invalid ajax socket: uid is invalid!") 278 279 http.Error(w, "Bad Request", 400) 280 return 281 } 282 283 // The user agents have to match. 284 if a.userAgent != userAgent { 285 log.L.WithFields(logrus.Fields{ 286 "remoteAddress": remoteAddr, 287 "userAgent": userAgent, 288 "uid": uid, 289 "clientUserAgent": userAgent, 290 "socketUserAgent": a.userAgent, 291 }).Warningf("ajax: client poll request: user agents do not match!") 292 293 http.Error(w, "Bad Request", 400) 294 return 295 } 296 297 // Check if the poll tokens matches. 298 // The poll token is the data value. 299 if a.pollToken != data { 300 log.L.WithFields(logrus.Fields{ 301 "remoteAddress": remoteAddr, 302 "userAgent": userAgent, 303 "uid": uid, 304 "clientPollToken": data, 305 "socketPollToken": a.pollToken, 306 }).Warningf("ajax: client poll request: poll tokens do not match!") 307 308 http.Error(w, "Bad Request", 400) 309 return 310 } 311 312 // Create a new poll token. 313 a.pollToken = utils.RandomString(ajaxPollTokenLength) 314 315 // Create a timeout timer for the poll. 316 timeout := time.NewTimer(ajaxPollTimeout) 317 318 defer func() { 319 // Stop the timeout timer. 320 timeout.Stop() 321 }() 322 323 // Send messages as soon as there are some available. 324 select { 325 case data := <-a.writeChan: 326 // Send the new poll token and message data to the client. 327 io.WriteString(w, a.pollToken+ajaxSocketDataDelimiter+data) 328 case <-timeout.C: 329 // Tell the client that this ajax connection has reached the timeout. 330 io.WriteString(w, ajaxPollCmdTimeout) 331 case <-a.closer.IsClosedChan: 332 // Tell the client that this ajax connection is closed. 333 io.WriteString(w, ajaxPollCmdClosed) 334 } 335 }