package fairwind import ( "encoding/json" "fmt" "io" "net" "net/http" "time" ) type HTTPServerRequest struct { Headers HTTPHeaders Data any } type HTTPServerResponse struct { Headers HTTPHeaders Status int Error error Encoding int Plain []byte Data any } func ResponsePlain(headers HTTPHeaders, plain []byte) *HTTPServerResponse { return &HTTPServerResponse{ Headers: headers, Plain: plain, Encoding: ENCODING_PLAIN, } } func ResponseJSON(headers HTTPHeaders, data any) *HTTPServerResponse { return &HTTPServerResponse{ Headers: headers, Data: data, Encoding: ENCODING_JSON, } } func ResponseErr(status int, err error) *HTTPServerResponse { return &HTTPServerResponse{ Status: status, Error: err, } } type HTTPServerCallback func(log *Log, request *HTTPServerRequest) *HTTPServerResponse type HTTPServerHandler struct { Callback HTTPServerCallback Data any Buffer int } type HTTPServerAction struct { Method int Path string } type HTTPServer struct { log *Log host string port string handlers map[HTTPServerAction]HTTPServerHandler } func NewHTTPServer(log *Log, host string, port string, handlers map[HTTPServerAction]HTTPServerHandler) *HTTPServer { return &HTTPServer{ log: log, host: host, port: port, handlers: handlers, } } func (this *HTTPServer) Start() error { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", this.host, this.port)) if err != nil { return err } channel := make(chan error) go func(listener net.Listener, channel chan<- error) { server := &http.Server{ Handler: this, } channel <- server.Serve(listener) }(listener, channel) err = <-channel if err != nil { return err } return nil } func (this *HTTPServer) ServeHTTP(responseStream http.ResponseWriter, requestStream *http.Request) { // Find handler method := METHOD_NONE switch requestStream.Method { case "GET": method = METHOD_GET case "POST": method = METHOD_POST } if method == METHOD_NONE { this.log.Error("invalid method", LogValue("method", requestStream.Method)) responseStream.WriteHeader(http.StatusInternalServerError) return } handler, ok := this.handlers[HTTPServerAction{Method: method, Path: requestStream.URL.Path}] if !ok { this.log.Error("handler not found", LogValue("path", requestStream.URL.Path)) responseStream.WriteHeader(http.StatusInternalServerError) return } // Parse GET query if method == METHOD_GET { query := map[string]string{} for key, values := range requestStream.URL.Query() { for _, value := range values { query[key] = value } } buffer, err := json.Marshal(query) if err != nil { this.log.Error("malformed request query", LogValue("path", requestStream.URL.Path), LogError(err)) responseStream.WriteHeader(http.StatusInternalServerError) return } err = json.Unmarshal(buffer, handler.Data) if err != nil { this.log.Error("malformed request query", LogValue("path", requestStream.URL.Path), LogError(err)) responseStream.WriteHeader(http.StatusInternalServerError) return } } // Parse POST body if method == METHOD_POST { size := 1 if handler.Buffer > 0 { size = handler.Buffer } buffer := make([]byte, 1024*size) n, err := requestStream.Body.Read(buffer) if err != io.EOF { this.log.Error("malformed request body", LogValue("path", requestStream.URL.Path), LogError(err)) responseStream.WriteHeader(http.StatusInternalServerError) return } err = json.Unmarshal(buffer[:n], handler.Data) if err != nil { this.log.Error("malformed request body", LogValue("path", requestStream.URL.Path), LogError(err)) responseStream.WriteHeader(http.StatusInternalServerError) return } } // Log query start := time.Now() defer func() { this.log.Debug( "request", LogValue("method", requestStream.Method), LogValue("path", requestStream.URL.Path), LogValue("duration", time.Since(start)), ) }() // Handle requestHeaders := HTTPHeaders{} for key, values := range requestStream.Header { for _, value := range values { requestHeaders[key] = value } } response := handler.Callback( this.log, &HTTPServerRequest{ Headers: requestHeaders, Data: handler.Data, }, ) // Headers for key, value := range response.Headers { responseStream.Header().Add(key, value) } if response.Encoding == ENCODING_PLAIN { responseStream.Header().Add("Content-Type", "text/plain") } if response.Encoding == ENCODING_JSON { responseStream.Header().Add("Content-Type", "application/json") } // Status if response.Status == 0 { responseStream.WriteHeader(http.StatusOK) } else { responseStream.WriteHeader(response.Status) } // Body if response.Encoding == ENCODING_PLAIN { _, err := responseStream.Write(response.Plain) if err != nil { this.log.Error("malformed response body", LogValue("path", requestStream.URL.Path), LogError(err)) return } } if response.Encoding == ENCODING_JSON { buffer, err := json.Marshal(response.Data) if err != nil { this.log.Error("malformed response body", LogValue("path", requestStream.URL.Path), LogError(err)) return } _, err = responseStream.Write(buffer) if err != nil { this.log.Error("malformed response body", LogValue("path", requestStream.URL.Path), LogError(err)) return } } }