First rev commit

master
Niels Westland 1 year ago
commit 486fa84f21

1
.gitignore vendored

@ -0,0 +1 @@
.idea/

@ -0,0 +1,160 @@
package nwweb
import (
"bytes"
"encoding/json"
"errors"
"git.nwestland.com/tools/nwjson"
"io"
)
var (
ErrCannotRead = errors.New("cannot read from body")
ErrCannotWrite = errors.New("cannot write to body")
ErrCannotConvertToBuffer = errors.New("cannot convert body to buffer")
)
type body struct {
parent any
readerWriter any
}
func newBody(parent any, readerWriter any) body {
return body{
parent: parent,
readerWriter: readerWriter,
}
}
func (b body) Read(p []byte) (int, error) {
if reader, ok := b.readerWriter.(io.Reader); ok {
return reader.Read(p)
}
return 0, ErrCannotRead
}
func (b body) Bytes() []byte {
if reader, ok := b.readerWriter.(io.Reader); ok {
data, _ := io.ReadAll(reader)
b.Close()
return data
}
return nil
}
func (b body) String() string {
return string(b.Bytes())
}
func (b body) Object() nwjson.Object {
if reader, ok := b.readerWriter.(io.Reader); ok {
return nwjson.ObjectFromStream(reader)
}
return nil
}
func (b body) Array() nwjson.Array {
if reader, ok := b.readerWriter.(io.Reader); ok {
return nwjson.ArrayFromStream(reader)
}
return nil
}
func (b body) UnmarshalJson(any any) error {
if reader, ok := b.readerWriter.(io.Reader); ok {
jd := json.NewDecoder(reader)
err := jd.Decode(&any)
b.Close()
return err
}
return ErrCannotRead
}
func (b body) Write(p []byte) (int, error) {
if writer, ok := b.readerWriter.(io.Writer); ok {
return writer.Write(p)
}
return 0, ErrCannotWrite
}
func (b body) WriteString(s string) (int, error) {
if writer, ok := b.readerWriter.(io.Writer); ok {
return writer.Write([]byte(s))
}
return 0, ErrCannotWrite
}
func (b body) WriteJsonError(errText string, statusCode int) error {
if response, ok := b.parent.(*Response); ok {
defer response.SetStatusCode(statusCode)
return b.MarshalJson(map[string]string{"Error": errText})
}
return ErrCannotWrite
}
func (b body) MarshalJson(any any) error {
switch b.parent.(type) {
case *Request:
b.parent.(*Request).Headers.SetContentType("application/json")
case *Response:
b.parent.(*Response).Headers.SetContentType("application/json")
}
if writer, ok := b.readerWriter.(io.Writer); ok {
je := json.NewEncoder(writer)
return je.Encode(any)
}
return ErrCannotWrite
}
func (b *body) Buffer() (*bytes.Buffer, error) {
switch b.readerWriter.(type) {
case *bytes.Buffer:
return b.readerWriter.(*bytes.Buffer), nil
case io.Reader:
buf := new(bytes.Buffer)
buf.ReadFrom(b.readerWriter.(io.Reader))
b.Close()
b.readerWriter = buf
return buf, nil
default:
return nil, ErrCannotConvertToBuffer
}
}
//TODO check if this is a good solution
func (b *body) Len() int {
var contentLength int
switch b.parent.(type) {
case *Request:
contentLength = b.parent.(*Request).Headers.ContentLength()
case *Response:
contentLength = b.parent.(*Response).Headers.ContentLength()
}
if contentLength == 0 {
if buf, err := b.Buffer(); err == nil {
return buf.Len()
}
}
return contentLength
}
func (b body) Close() error {
if readCloser, ok := b.readerWriter.(io.ReadCloser); ok {
return readCloser.Close()
}
return nil
}

@ -0,0 +1,59 @@
package nwweb
import (
"crypto/tls"
"fmt"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
)
var DefaultClient = NewClient()
type Client struct {
httpClient *http.Client
httpCookieJar http.CookieJar
}
func NewClient() *Client {
httpCookieJar, _ := cookiejar.New(nil)
return &Client{
httpClient: &http.Client{
Transport: &http.Transport{},
Jar: httpCookieJar,
},
httpCookieJar: httpCookieJar,
}
}
func (c Client) Do(request *Request) *Response {
request.finalize()
httpResponse, err := c.httpClient.Do(request.httpRequest)
if err != nil {
return nil
}
return newResponseClient(httpResponse)
}
func (c *Client) SetProxy(hostname string, port int) {
proxyUrl, _ := url.Parse(fmt.Sprintf("%s:%d", hostname, port))
c.httpClient.Transport.(*http.Transport).Proxy = http.ProxyURL(proxyUrl)
}
func (c *Client) SetSSLChecks(enabled bool) {
c.httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: !enabled}
}
func (c *Client) SetTLSVersion(maxVersion uint16) {
tlsConfig := &tls.Config{MaxVersion: maxVersion}
c.httpClient.Transport.(*http.Transport).DialTLS = func(network, addr string) (net.Conn, error) {
return tls.Dial(network, addr, tlsConfig)
}
}
func (c *Client) DisableHTTP2() {
c.httpClient.Transport.(*http.Transport).TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper)
}

@ -0,0 +1,39 @@
package nwweb
import "git.nwestland.com/tools/nwjson"
type convertable struct {
stringVal func(string) string
}
func (c convertable) String(key string) string {
return c.stringVal(key)
}
func (c convertable) Bytes(key string) []byte {
return stringToBytes(c.String(key))
}
func (c convertable) Int(key string) int {
return stringToInt(c.String(key))
}
func (c convertable) Bool(key string) bool {
return stringToBool(c.String(key))
}
func (c convertable) Float32(key string) float32 {
return stringToFloat32(c.String(key))
}
func (c convertable) Float64(key string) float64 {
return stringToFloat64(c.String(key))
}
func (c convertable) Object(key string) nwjson.Object {
return stringToObject(c.String(key))
}
func (c convertable) Array(key string) nwjson.Array {
return stringToArray(c.String(key))
}

@ -0,0 +1,33 @@
package nwweb
import (
"net/http"
"net/url"
)
type form struct {
convertable
postForm url.Values
httpRequest *http.Request
}
func newForm(httpRequest *http.Request) form {
if httpRequest.Form == nil {
httpRequest.ParseForm()
}
f := form{httpRequest: httpRequest}
f.stringVal = func(key string) string {
return f.httpRequest.Form.Get(key)
}
return f
}
func (f *form) Set(key string, value any) {
if f.postForm == nil {
f.postForm = url.Values{}
}
f.postForm.Set(key, anyToString(value))
}

@ -0,0 +1,5 @@
module git.nwestland.com/tools/nwweb
go 1.18
require git.nwestland.com/tools/nwjson v0.0.0-20220420120036-0c00931366e7

@ -0,0 +1,2 @@
git.nwestland.com/tools/nwjson v0.0.0-20220420120036-0c00931366e7 h1:f4eqAeUQ8B94VvXPdKy8lJW1Z0K/kgA0LdRD3ktRH90=
git.nwestland.com/tools/nwjson v0.0.0-20220420120036-0c00931366e7/go.mod h1:eMzX1O7T63BIw+CigFeul8xaqrY8DDkHU83N4NTTTgc=

@ -0,0 +1,62 @@
package nwweb
import (
"encoding/base64"
"net/http"
)
type headers struct {
httpObj any
}
func newHeaders(httpObj any) headers {
return headers{httpObj: httpObj}
}
func (h headers) Get(key string) string {
switch h.httpObj.(type) {
case *http.Request:
return h.httpObj.(*http.Request).Header.Get(key)
case *http.Response:
return h.httpObj.(*http.Response).Header.Get(key)
default:
return ""
}
}
func (h headers) ContentLength() int {
return stringToInt(h.Get("Content-Length"))
}
func (h headers) Set(key string, value any) {
switch h.httpObj.(type) {
case *http.Request:
h.httpObj.(*http.Request).Header.Set(key, anyToString(value))
case http.ResponseWriter:
h.httpObj.(http.ResponseWriter).Header().Set(key, anyToString(value))
}
}
func (h headers) SetContentType(mimeType string) {
h.Set("Content-Type", mimeType)
}
func (h headers) SetContentLength(contentLength int) {
switch h.httpObj.(type) {
case *http.Request:
h.httpObj.(*http.Request).ContentLength = int64(contentLength)
//http.ResponseWriter automatically sets its content length
}
}
func (h headers) SetBasicAuth(username, password string) {
h.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(username+":"+password)))
}
func (h headers) SetBearerAuth(token string) {
h.Set("Authorization", "Bearer "+token)
}
func (h headers) SetUserAgent(userAgent string) {
h.Set("User-Agent", userAgent)
}

@ -0,0 +1,5 @@
package nwweb
type IController interface {
GetRoutes() []Route
}

@ -0,0 +1,70 @@
package nwweb
import (
"fmt"
"git.nwestland.com/tools/nwjson"
"strconv"
)
const (
GET = "GET"
HEAD = "HEAD"
POST = "POST"
PUT = "PUT"
PATCH = "PATCH"
DELETE = "DELETE"
CONNECT = "CONNECT"
OPTIONS = "OPTIONS"
TRACE = "TRACE"
)
const (
TLS10 = 0x0301
TLS11 = 0x0302
TLS12 = 0x0303
TLS13 = 0x0304
SSL30 = 0x0300
)
func stringToBytes(stringVal string) []byte {
return []byte(stringVal)
}
func stringToInt(stringVal string) (val int) {
val, _ = strconv.Atoi(stringVal)
return
}
func stringToBool(stringVal string) (val bool) {
val, _ = strconv.ParseBool(stringVal)
return
}
func stringToFloat32(stringVal string) float32 {
value, _ := strconv.ParseFloat(stringVal, 32)
return float32(value)
}
func stringToFloat64(stringVal string) (val float64) {
val, _ = strconv.ParseFloat(stringVal, 64)
return
}
func stringToObject(stringVal string) nwjson.Object {
return nwjson.ObjectFromString(stringVal)
}
func stringToArray(stringVal string) nwjson.Array {
return nwjson.ArrayFromString(stringVal)
}
func anyToString(value any) string {
switch value.(type) {
case nwjson.Object:
return value.(nwjson.Object).ToString()
case nwjson.Array:
return value.(nwjson.Array).ToString()
default:
return fmt.Sprintf("%v", value)
}
}

@ -0,0 +1,53 @@
package nwweb
import (
"fmt"
"git.nwestland.com/tools/nwjson"
"io"
"net/http"
"testing"
)
func Test(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://www.postman-echo.com/get", nil)
if err != nil {
fmt.Println(err)
}
resp, err := http.DefaultClient.Do(req)
data, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println(len(data))
}
func TestNew(t *testing.T) {
req := NewRequest(POST, "https://www.postman-echo.com/post")
req.QueryParameters.Set("query", "test")
req.Body.MarshalJson(nwjson.Object{"Username": "johndoe"})
resp := DefaultClient.Do(req)
fmt.Println(resp.Body.Object().ToPrettyString())
}
func TestServer(t *testing.T) {
server := NewServer("localhost", 8080)
server.RegisterController(TestController{})
server.Start()
}
type TestController struct{}
func (e TestController) GetRoutes() []Route {
return []Route{
{
Pattern: "/",
Handler: e.Test,
Method: GET,
},
}
}
func (e TestController) Test(response *Response, request *Request) {
response.Body.WriteString("Hi there")
}

@ -0,0 +1,33 @@
package nwweb
import (
"fmt"
"net/http"
"net/url"
)
type queryParameters struct {
convertable
httpRequest *http.Request
}
func newQueryParameters(httpRequest *http.Request) queryParameters {
q := queryParameters{httpRequest: httpRequest}
q.stringVal = func(key string) string {
return q.httpRequest.URL.Query().Get(key)
}
return q
}
func (q queryParameters) Set(key string, value any) {
if len(q.httpRequest.URL.RawQuery) > 0 {
q.httpRequest.URL.RawQuery += fmt.Sprintf("&%s=%s", key, url.QueryEscape(fmt.Sprintf("%v", value)))
} else {
q.httpRequest.URL.RawQuery = fmt.Sprintf("%s=%s", key, url.QueryEscape(fmt.Sprintf("%v", value)))
}
}
func (q queryParameters) ReplaceWith(query url.Values) {
q.httpRequest.URL.RawQuery = query.Encode()
}

@ -0,0 +1,77 @@
package nwweb
import (
"bytes"
"fmt"
"io"
"net"
"net/http"
)
type Request struct {
httpRequest *http.Request
Body body
Headers headers
QueryParameters queryParameters
Form form
}
func NewRequest(method, urlFmt string, args ...any) *Request {
var httpRequest *http.Request
if len(args) > 0 {
httpRequest, _ = http.NewRequest(method, fmt.Sprintf(urlFmt, args...), nil)
} else {
httpRequest, _ = http.NewRequest(method, urlFmt, nil)
}
r := &Request{
httpRequest: httpRequest,
Headers: newHeaders(httpRequest),
QueryParameters: newQueryParameters(httpRequest),
Form: newForm(httpRequest),
}
r.Body = newBody(r, new(bytes.Buffer))
return r
}
func newRequestServer(httpRequest *http.Request) *Request {
r := &Request{
httpRequest: httpRequest,
Headers: newHeaders(httpRequest),
QueryParameters: newQueryParameters(httpRequest),
Form: newForm(httpRequest),
}
r.Body = newBody(r, httpRequest.Body)
return r
}
func (r Request) Method() string {
return r.httpRequest.Method
}
func (r Request) Cookie(key string) *http.Cookie {
cookie, _ := r.httpRequest.Cookie(key)
return cookie
}
func (r Request) IP() net.IP {
if ipString, _, err := net.SplitHostPort(r.httpRequest.RemoteAddr); err == nil {
return net.ParseIP(ipString)
}
return nil
}
func (r Request) SetCookie(cookie *http.Cookie) {
r.httpRequest.AddCookie(cookie)
}
func (r *Request) finalize() {
r.httpRequest.ContentLength = int64(r.Body.Len())
r.httpRequest.Body = r.Body
r.httpRequest.GetBody = func() (io.ReadCloser, error) {
return r.Body, nil
}
}

@ -0,0 +1,67 @@
package nwweb
import "net/http"
type Response struct {
httpObj any
Body body
Headers headers
}
func newResponseClient(httpResponse *http.Response) *Response {
r := &Response{
httpObj: httpResponse,
Headers: newHeaders(httpResponse),
}
r.Body = newBody(r, httpResponse.Body)
return r
}
func newResponseServer(httpResponseWriter http.ResponseWriter) *Response {
r := &Response{
httpObj: httpResponseWriter,
Headers: newHeaders(httpResponseWriter),
}
r.Body = newBody(r, httpResponseWriter)
return r
}
func (r Response) StatusCode() int {
if response, ok := r.httpObj.(*http.Response); ok {
return response.StatusCode
}
return 0
}
func (r Response) Cookie(name string) *http.Cookie {
if response, ok := r.httpObj.(*http.Response); ok {
for _, cookie := range response.Cookies() {
if cookie.Name == name {
return cookie
}
}
}
return nil
}
func (r Response) SetStatusCode(statusCode int) {
if responseWriter, ok := r.httpObj.(http.ResponseWriter); ok {
responseWriter.WriteHeader(statusCode)
}
}
func (r Response) SetCookie(cookie *http.Cookie) {
if responseWriter, ok := r.httpObj.(http.ResponseWriter); ok {
http.SetCookie(responseWriter, cookie)
}
}
func (r Response) Redirect(request *Request, toUrl string, statusCode int) {
if responseWriter, ok := r.httpObj.(http.ResponseWriter); ok {
http.Redirect(responseWriter, request.httpRequest, toUrl, statusCode)
}
}

@ -0,0 +1,7 @@
package nwweb
type Route struct {
Pattern string
Handler func(response *Response, request *Request)
Method string
}

@ -0,0 +1,43 @@
package nwweb
import (
"fmt"
"net/http"
)
type Server struct {
httpServeMux *http.ServeMux
httpServer *http.Server
}
func NewServer(hostname string, port int) *Server {
mux := http.NewServeMux()
return &Server{
httpServeMux: mux,
httpServer: &http.Server{
Addr: fmt.Sprintf("%s:%d", hostname, port),
Handler: mux,
},
}
}
func (s *Server) RegisterFunc(pattern, method string, handler func(response *Response, request *Request)) {
s.httpServeMux.HandleFunc(pattern, func(httpResponseWriter http.ResponseWriter, httpRequest *http.Request) {
if len(method) == 0 || method == "*" || method == httpRequest.Method {
handler(newResponseServer(httpResponseWriter), newRequestServer(httpRequest))
return
}
httpResponseWriter.WriteHeader(http.StatusMethodNotAllowed)
})
}
func (s *Server) RegisterController(controller IController) {
for _, route := range controller.GetRoutes() {
s.RegisterFunc(route.Pattern, route.Method, route.Handler)
}
}
func (s *Server) Start() error {
return s.httpServer.ListenAndServe()
}
Loading…
Cancel
Save