|
@@ -0,0 +1,240 @@
|
|
|
|
|
+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
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|