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  }