Added basic functionality
This commit is contained in:
parent
1fa70cbab0
commit
efa45214c3
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,10 +1,3 @@
|
|||||||
# ---> Go.AllowList
|
|
||||||
# Allowlisting gitignore template for GO projects prevents us
|
|
||||||
# from adding various unwanted local files, such as generated
|
|
||||||
# files, developer configurations or IDE-specific files etc.
|
|
||||||
#
|
|
||||||
# Recommended: Go.AllowList.gitignore
|
|
||||||
|
|
||||||
# Ignore everything
|
# Ignore everything
|
||||||
*
|
*
|
||||||
|
|
||||||
@ -18,8 +11,5 @@
|
|||||||
!README.md
|
!README.md
|
||||||
!LICENSE
|
!LICENSE
|
||||||
|
|
||||||
# !Makefile
|
|
||||||
|
|
||||||
# ...even if they are in subdirectories
|
# ...even if they are in subdirectories
|
||||||
!*/
|
!*/
|
||||||
|
|
||||||
|
51
Context.go
Normal file
51
Context.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package aero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxParams defines the maximum number of parameters per route.
|
||||||
|
const maxParams = 16
|
||||||
|
|
||||||
|
// Context represents the interface for a request & response context.
|
||||||
|
type Context interface {
|
||||||
|
Bytes([]byte) error
|
||||||
|
Error(int, error) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// context represents a request & response context.
|
||||||
|
type context struct {
|
||||||
|
request *http.Request
|
||||||
|
response http.ResponseWriter
|
||||||
|
paramNames [maxParams]string
|
||||||
|
paramValues [maxParams]string
|
||||||
|
paramCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// newContext returns a new context from the pool.
|
||||||
|
func newContext(request *http.Request, response http.ResponseWriter) *context {
|
||||||
|
ctx := contextPool.Get().(*context)
|
||||||
|
ctx.request = request
|
||||||
|
ctx.response = response
|
||||||
|
ctx.paramCount = 0
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes responds with a raw byte slice.
|
||||||
|
func (ctx *context) Bytes(body []byte) error {
|
||||||
|
_, err := ctx.response.Write(body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error is used for sending error messages to the client.
|
||||||
|
func (ctx *context) Error(status int, err error) error {
|
||||||
|
ctx.response.WriteHeader(status)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// addParameter adds a new parameter to the context.
|
||||||
|
func (ctx *context) addParameter(name string, value string) {
|
||||||
|
ctx.paramNames[ctx.paramCount] = name
|
||||||
|
ctx.paramValues[ctx.paramCount] = value
|
||||||
|
ctx.paramCount++
|
||||||
|
}
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 go
|
Copyright (c) 2023 Eduard Urbach
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
49
Server.go
Normal file
49
Server.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package aero
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.akyoto.dev/go/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler is a function that deals with the given request/response context.
|
||||||
|
type Handler func(Context) error
|
||||||
|
|
||||||
|
// Server represents a single web service.
|
||||||
|
type Server struct {
|
||||||
|
router *router.Router[Handler]
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new server.
|
||||||
|
func New() *Server {
|
||||||
|
return &Server{
|
||||||
|
router: router.New[Handler](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get registers your function to be called when the given GET path has been requested.
|
||||||
|
func (server *Server) Get(path string, handler Handler) {
|
||||||
|
server.router.Add("GET", path, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP responds to the given request.
|
||||||
|
func (server *Server) ServeHTTP(response http.ResponseWriter, request *http.Request) {
|
||||||
|
ctx := newContext(request, response)
|
||||||
|
handler := server.router.LookupNoAlloc(request.Method, request.URL.Path, ctx.addParameter)
|
||||||
|
|
||||||
|
if handler == nil {
|
||||||
|
response.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprint(response, http.StatusText(http.StatusNotFound))
|
||||||
|
contextPool.Put(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := handler(ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(response, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
contextPool.Put(ctx)
|
||||||
|
}
|
54
Server_test.go
Normal file
54
Server_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package aero_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.akyoto.dev/go/aero"
|
||||||
|
"git.akyoto.dev/go/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServer(t *testing.T) {
|
||||||
|
server := aero.New()
|
||||||
|
|
||||||
|
server.Get("/", func(ctx aero.Context) error {
|
||||||
|
return ctx.Bytes([]byte("Hello"))
|
||||||
|
})
|
||||||
|
|
||||||
|
server.Get("/blog/:post", func(ctx aero.Context) error {
|
||||||
|
return ctx.Bytes([]byte("Hello"))
|
||||||
|
})
|
||||||
|
|
||||||
|
server.Get("/error", func(ctx aero.Context) error {
|
||||||
|
return ctx.Error(http.StatusUnauthorized, errors.New("Not logged in"))
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
URL string
|
||||||
|
Status int
|
||||||
|
Body string
|
||||||
|
}{
|
||||||
|
{URL: "/", Status: http.StatusOK, Body: "Hello"},
|
||||||
|
{URL: "/blog/post", Status: http.StatusOK, Body: "Hello"},
|
||||||
|
{URL: "/error", Status: http.StatusUnauthorized, Body: "Not logged in"},
|
||||||
|
{URL: "/not-found", Status: http.StatusNotFound, Body: http.StatusText(http.StatusNotFound)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run("example.com"+test.URL, func(t *testing.T) {
|
||||||
|
request := httptest.NewRequest(http.MethodGet, test.URL, nil)
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
server.ServeHTTP(response, request)
|
||||||
|
|
||||||
|
result := response.Result()
|
||||||
|
assert.Equal(t, result.StatusCode, test.Status)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(result.Body)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.DeepEqual(t, string(body), test.Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
7
go.mod
Normal file
7
go.mod
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module git.akyoto.dev/go/aero
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require git.akyoto.dev/go/assert v0.1.2
|
||||||
|
|
||||||
|
require git.akyoto.dev/go/router v0.1.1
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
git.akyoto.dev/go/assert v0.1.2 h1:3paz/5z/JcGK/2K9J+pVh5Jwt2gYfJQG+P5OE9/jB7Y=
|
||||||
|
git.akyoto.dev/go/assert v0.1.2/go.mod h1:Zr/UFuiqmqRmFFgpBGwF71jbzb6iYJfXFeePYHGtWsg=
|
||||||
|
git.akyoto.dev/go/router v0.1.1 h1:6fHjzv59MKMhO2DsM90mkI5hy5PrcjV4WeD1Vk1xXXs=
|
||||||
|
git.akyoto.dev/go/router v0.1.1/go.mod h1:IwwEUJU2ExmozpZKMbDOKdiVT516oAijnxGDg9kvBt4=
|
Loading…
Reference in New Issue
Block a user