Added basic functionality

This commit is contained in:
Eduard Urbach 2023-07-18 18:02:57 +02:00
parent 1fa70cbab0
commit efa45214c3
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
9 changed files with 177 additions and 12 deletions

12
.gitignore vendored
View File

@ -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
*
@ -18,8 +11,5 @@
!README.md
!LICENSE
# !Makefile
# ...even if they are in subdirectories
!*/
!*/

51
Context.go Normal file
View 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++
}

View File

@ -1,6 +1,6 @@
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:

View File

@ -1,2 +1,3 @@
# aero
High-performance web framework.

49
Server.go Normal file
View 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
View 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
View 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
View 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=

9
pool.go Normal file
View File

@ -0,0 +1,9 @@
package aero
import "sync"
var contextPool = sync.Pool{
New: func() any {
return &context{}
},
}