From 0ceaf17c342ec29c26b9a5a77627efd20e2a34aa Mon Sep 17 00:00:00 2001 From: some Date: Mon, 7 Oct 2024 17:39:42 -0400 Subject: [PATCH] Initial commit --- .editorconfig | 35 +++++++++++++++++++ LICENSE | 25 +++++++++++++ go.mod | 5 +++ go.sum | 2 ++ header.go | 36 +++++++++++++++++++ prototype.go | 43 +++++++++++++++++++++++ server.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 243 insertions(+) create mode 100644 .editorconfig create mode 100644 LICENSE create mode 100644 go.mod create mode 100644 go.sum create mode 100644 header.go create mode 100644 prototype.go create mode 100644 server.go diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0cf80d3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +root = true + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = space +indent_size = 2 + +[*.sh] +indent_style = space +indent_size = 4 + +[{*.html,*.js,*.css,*.scss}] +indent_style = space +indent_size = 4 + +[Makefile] +indent_style = tab +indent_size = 8 + +[{{*.,}[Dd]ockerfile{.*,},{*.,}[Cc]ontainerfile{.*,}}] +indent_style = space +indent_size = 4 + +[*.proto] +indent_style = space +indent_size = 2 + +[{*.go,go.mod}] +indent_style = tab +indent_size = 8 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ed0e7b3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2024, some +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6cc183f --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module somehole.com/common/router + +go 1.23.1 + +require somehole.com/common/log v0.1.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2fc089d --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +somehole.com/common/log v0.1.3 h1:2PAui0+5EryTAHqVUZQeepLcJTGssHKz2OL+jBknHI0= +somehole.com/common/log v0.1.3/go.mod h1:NS2eHnN120GA6oFbBm3XhB5yHww0eXTbLuMQYZxYNyA= diff --git a/header.go b/header.go new file mode 100644 index 0000000..2811338 --- /dev/null +++ b/header.go @@ -0,0 +1,36 @@ +package router + +import "strings" + +type Header map[string][]string + +func (h Header) Get(key string) (value string) { + v, ok := h[key] + if !ok { + return + } + return strings.Join(v, ", ") +} + +func (h Header) Set(key string, value string) { + h[key] = []string{value} +} + +func (h Header) Add(key string, value string) { + h[key] = append(h[key], value) +} + +func (h Header) Merge(header Header) { + for key, value := range header { + if _, exists := h[key]; exists { + continue + } + h[key] = value + } +} + +func (h Header) MergeOverwrite(header Header) { + for key, value := range header { + h[key] = value + } +} diff --git a/prototype.go b/prototype.go new file mode 100644 index 0000000..e5b5441 --- /dev/null +++ b/prototype.go @@ -0,0 +1,43 @@ +package router + +import "io" + +type Request interface { + Allowed(method string) ErrorResponse + Header() Header + ReadBody(body io.ReadCloser) ErrorResponse + ParseForm(values map[string][]string) ErrorResponse +} + +type PrototypeRequest interface { + Request() Request +} + +type Response interface { + Header() Header + Response() (body []byte) +} + +type PrototypeResponse interface { + Response() Response +} + +type ErrorResponse interface { + Ok() bool + HttpStatus() int + ErrorResponse() []byte + String() string +} + +type PrototypeErrorResponse interface { + ErrorResponse() ErrorResponse + AllowedError() ErrorResponse + ReadError() ErrorResponse + ParseError() ErrorResponse +} + +type Prototype struct { + PrototypeRequest + PrototypeResponse + PrototypeErrorResponse +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..9bf955c --- /dev/null +++ b/server.go @@ -0,0 +1,97 @@ +package router + +import ( + "net/http" + + "somehole.com/common/log" +) + +type stage uint8 + +const ( + pre stage = iota + main + post + numStages +) + +type ServeFunc func(req Request, res Response) (errRes ErrorResponse) + +type server struct { + prototype Prototype + logger log.Logger + serve [numStages][]ServeFunc +} + +func NewServer(prototype Prototype, logger log.Logger, serve ServeFunc) (srv *server) { + srv = &server{ + prototype: prototype, + logger: logger, + serve: [numStages][]ServeFunc{}, + } + srv.serve[main] = append(srv.serve[main], serve) + return srv +} + +func (srv *server) addServeFunc(when stage, serve ServeFunc) *server { + if srv.serve[when] == nil { + srv.serve[when] = make([]ServeFunc, 0) + } + srv.serve[when] = append(srv.serve[when], serve) + return srv +} + +func (srv *server) PreServeFunc(serve ServeFunc) *server { + return srv.addServeFunc(pre, serve) +} + +func (srv *server) AddServeFunc(serve ServeFunc) *server { + return srv.addServeFunc(main, serve) +} + +func (srv *server) PostServeFunc(serve ServeFunc) *server { + return srv.addServeFunc(post, serve) +} + +func (srv *server) handleError(errRes ErrorResponse, w http.ResponseWriter) (ok bool) { + if !errRes.Ok() { + w.WriteHeader(errRes.HttpStatus()) + w.Write(errRes.ErrorResponse()) + srv.logger.Logf(log.LevelError, errRes.String()) + return false + } + return true +} + +func (srv *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + pro := srv.prototype + req := pro.Request() + if ok := srv.handleError(req.Allowed(r.Method), w); !ok { + return + } + req.Header().MergeOverwrite(Header(r.Header)) + if ok := srv.handleError(req.ReadBody(r.Body), w); !ok { + return + } + r.ParseForm() + if ok := srv.handleError(req.ParseForm(r.Form), w); !ok { + return + } + res := pro.Response() + for _, stage := range srv.serve { + for _, s := range stage { + if ok := srv.handleError(s(req, res), w); !ok { + return + } + header := res.Header() + if header != nil { + for key, value := range res.Header() { + for _, v := range value { + w.Header().Add(key, v) + } + } + } + } + } + w.Write(res.Response()) +}