package fairwind import ( "encoding/json" "fmt" "io" "net" "net/http" "reflect" "time" ) type HTTPServerRequest struct { Headers HTTPHeaders Data any } type HTTPServerResponse struct { Headers HTTPHeaders Status int Error error Encoding int Plain []byte Data any } func ResponseOK() *HTTPServerResponse { return &HTTPServerResponse{ Headers: HTTPHeaders{}, Plain: []byte{}, Encoding: ENCODING_PLAIN, } } 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(request *HTTPServerRequest) *HTTPServerResponse type HTTPServerHandler struct { Callback HTTPServerCallback Data any Buffer int Validators map[string][]Validator } 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 } handlerSpec, 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 } handler := HTTPServerHandler{} copyStruct(&handler, &handlerSpec) handlerSpec = handler // Parse GET query if method == METHOD_GET && handler.Data != nil { // TODO: support arrays query := map[string]any{} 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 body", 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 body", LogValue("path", requestStream.URL.Path), LogError(err)) responseStream.WriteHeader(http.StatusInternalServerError) return } } // Parse POST body if method == METHOD_POST && handler.Data != nil { 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 } if !validateData(handler) { this.log.Error("malformed validation failed", LogValue("path", requestStream.URL.Path), LogError(err)) responseStream.WriteHeader(http.StatusNotAcceptable) 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( &HTTPServerRequest{ Headers: requestHeaders, Data: handler.Data, }, ) if response.Error != nil { this.log.Error("error serving request", LogError(response.Error)) } // 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 } } } func copyStruct(dst interface{}, src interface{}) { dstVal := reflect.ValueOf(dst) srcVal := reflect.ValueOf(src) if dstVal.Kind() != reflect.Ptr || dstVal.Elem().Kind() != reflect.Struct { return } if srcVal.Kind() == reflect.Ptr { srcVal = srcVal.Elem() } if srcVal.Kind() != reflect.Struct { return } dstVal = dstVal.Elem() for i := 0; i < srcVal.NumField(); i++ { srcField := srcVal.Field(i) fieldName := srcVal.Type().Field(i).Name dstField := dstVal.FieldByName(fieldName) if dstField.IsValid() && dstField.CanSet() && dstField.Type() == srcField.Type() { dstField.Set(srcField) } } } func validateData(handler HTTPServerHandler) bool { if handler.Validators == nil { return true } value := reflect.ValueOf(handler.Data) if value.Kind() != reflect.Ptr { return true } value = value.Elem() if value.Kind() != reflect.Struct { return true } for field := range value.Fields() { validators, ok := handler.Validators[field.Name] if !ok { continue } if len(validators) == 0 { continue } for _, validator := range validators { if validator.Validate(value.Interface()) { continue } return false } } return true }