Categorygithub.com/joaosoft/web
repositorypackage
0.0.0-20240321165800-ab860bfd0ff2
Repository: https://github.com/joaosoft/web.git
Documentation: pkg.go.dev

# Packages

No description provided by the author

# README

web

Build Status | codecov | Go Report Card | GoDoc

A simple and fast web server and client.

If i miss something or you have something interesting, please be part of this project. Let me know! My contact is at the end.

With support for

  • Common http methods
  • Single/Multiple File Upload
  • Single/Multiple File Download
  • Middlewares
  • Filters

With support for methods

  • HEAD
  • GET
  • POST
  • PUT
  • DELETE
  • PATCH
  • COPY
  • CONNECT
  • OPTIONS
  • TRACE
  • LINK
  • UNLINK
  • PURGE
  • LOCK
  • UNLOCK
  • PROPFIND
  • VIEW
  • ANY (used only on filters)

With authentication types

  • basic
  • jwt

With attachment modes

  • [default] zip files when returns more then one file
    • on client WithClientAttachmentMode(web.MultiAttachmentModeZip)
    • on server WithServerAttachmentMode(web.MultiAttachmentModeZip)
  • [experimental] returns attachmentes splited by a boundary defined on header Content-Type
    • on client WithClientAttachmentMode(web.MultiAttachmentModeBoundary)
    • on server WithServerAttachmentMode(web.MultiAttachmentModeBoundary)

Go

go get github.com/joaosoft/web

Usage

This examples are available in the project at web/examples

Server

func main() {
	// create a new server
	w, err := web.NewServer(web.WithServerMultiAttachmentMode(web.MultiAttachmentModeBoundary))
	if err != nil {
		panic(err)
	}

	claims := jwt.Claims{"name": "joao", "age": 30}

	keyFunc := func(token *jwt.Token) (interface{}, error) {
		return []byte("bananas"), nil
	}

	checkFunc := func(c jwt.Claims) (bool, error) {
		if claims["name"] == c["name"].(string) &&
			claims["age"] == int(c["age"].(float64)) {
			return true, nil
		}
		return false, fmt.Errorf("invalid jwt session token")
	}

	// add filters
	w.AddMiddlewares(MyMiddlewareOne(), MyMiddlewareTwo())
	w.AddFilter("/hello/:name", web.PositionBefore, MyFilterOne(), web.MethodPost)
	w.AddFilter("/hello/:name/upload", web.PositionBefore, MyFilterTwo(), web.MethodPost)
	w.AddFilter("*", web.PositionBefore, MyFilterThree(), web.MethodPost)

	w.AddFilter("/hello/:name", web.PositionBefore, MyFilterTwo(), web.MethodGet)

	w.AddFilter("/form-data", web.PositionAfter, MyFilterThree(), web.MethodAny)

	// add routes
	w.AddRoutes(
		web.NewRoute(web.MethodOptions, "*", HandlerHelloForOptions, web.MiddlewareOptions()),
		web.NewRoute(web.MethodGet, "/auth-basic", HandlerForGet, web.MiddlewareCheckAuthBasic("joao", "ribeiro")),
		web.NewRoute(web.MethodGet, "/auth-jwt", HandlerForGet, web.MiddlewareCheckAuthJwt(keyFunc, checkFunc)),
		web.NewRoute(web.MethodHead, "/hello/:name", HandlerHelloForHead),
		web.NewRoute(web.MethodGet, "/hello/:name", HandlerHelloForGet, MyMiddlewareThree()),
		web.NewRoute(web.MethodPost, "/hello/:name", HandlerHelloForPost),
		web.NewRoute(web.MethodPut, "/hello/:name", HandlerHelloForPut),
		web.NewRoute(web.MethodDelete, "/hello/:name", HandlerHelloForDelete),
		web.NewRoute(web.MethodPatch, "/hello/:name", HandlerHelloForPatch),
		web.NewRoute(web.MethodCopy, "/hello/:name", HandlerHelloForCopy),
		web.NewRoute(web.MethodConnect, "/hello/:name", HandlerHelloForConnect),
		web.NewRoute(web.MethodOptions, "/hello/:name", HandlerHelloForOptions, web.MiddlewareOptions()),
		web.NewRoute(web.MethodTrace, "/hello/:name", HandlerHelloForTrace),
		web.NewRoute(web.MethodLink, "/hello/:name", HandlerHelloForLink),
		web.NewRoute(web.MethodUnlink, "/hello/:name", HandlerHelloForUnlink),
		web.NewRoute(web.MethodPurge, "/hello/:name", HandlerHelloForPurge),
		web.NewRoute(web.MethodLock, "/hello/:name", HandlerHelloForLock),
		web.NewRoute(web.MethodUnlock, "/hello/:name", HandlerHelloForUnlock),
		web.NewRoute(web.MethodPropFind, "/hello/:name", HandlerHelloForPropFind),
		web.NewRoute(web.MethodView, "/hello/:name", HandlerHelloForView),
		web.NewRoute(web.MethodGet, "/hello/:name/download", HandlerHelloForDownloadFiles),
		web.NewRoute(web.MethodGet, "/hello/:name/download/one", HandlerHelloForDownloadOneFile),
		web.NewRoute(web.MethodPost, "/hello/:name/upload", HandlerHelloForUploadFiles),
		web.NewRoute(web.MethodGet, "/form-data", HandlerFormDataForGet),
		web.NewRoute(web.MethodGet, "/url-form-data", HandlerUrlFormDataForGet),
	)

	w.AddNamespace("/p").AddRoutes(
		web.NewRoute(web.MethodGet, "/hello/:name", HandlerHelloForGet, MyMiddlewareFour()),
	)

	// start the server
	if err := w.Start(); err != nil {
		panic(err)
	}
}

func MyFilterOne() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE FILTER ONE")
			return next(ctx)
		}
	}
}

func MyFilterTwo() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE FILTER TWO")
			return next(ctx)
		}
	}
}

func MyFilterThree() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE FILTER THREE")
			return next(ctx)
		}
	}
}

func MyMiddlewareOne() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE MIDDLEWARE ONE")
			return next(ctx)
		}
	}
}

func MyMiddlewareTwo() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE MIDDLEWARE TWO")
			return next(ctx)
		}
	}
}

func MyMiddlewareThree() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE MIDDLEWARE THREE")
			return next(ctx)
		}
	}
}

func MyMiddlewareFour() web.MiddlewareFunc {
	return func(next web.HandlerFunc) web.HandlerFunc {
		return func(ctx *web.Context) error {
			fmt.Println("HELLO I'M THE MIDDLEWARE FOUR")
			return next(ctx)
		}
	}
}

func HandlerForGet(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR GET")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \"guest\" }"),
	)
}

func HandlerHelloForHead(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR HEAD")

	return ctx.Response.NoContent(web.StatusOK)
}

func HandlerHelloForGet(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR GET")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForPost(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR POST")

	data := struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}{}
	ctx.Request.Bind(&data)
	fmt.Printf("DATA: %+v", data)

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForPut(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR PUT")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForDelete(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR DELETE")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForPatch(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR PATCH")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForCopy(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR COPY")

	return ctx.Response.NoContent(web.StatusOK)
}

func HandlerHelloForConnect(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR CONNECT")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForOptions(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR OPTIONS")

	return ctx.Response.NoContent(web.StatusNoContent)
}

func HandlerHelloForTrace(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR TRACE")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForLink(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR LINK")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForUnlink(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR UNLINK")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForPurge(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR PURGE")

	return ctx.Response.NoContent(web.StatusOK)
}

func HandlerHelloForLock(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR LOCK")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForUnlock(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR UNLOCK")

	return ctx.Response.NoContent(web.StatusOK)
}
func HandlerHelloForPropFind(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR PROPFIND")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForView(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR VIEW")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForDownloadOneFile(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR DOWNLOAD ONE FILE")

	dir, _ := os.Getwd()
	body, _ := web.ReadFile(fmt.Sprintf("%s%s", dir, "/examples/data/a.text"), nil)
	ctx.Response.Attachment("text_a.text", body)

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForDownloadFiles(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR DOWNLOAD FILES")

	dir, _ := os.Getwd()
	body, _ := web.ReadFile(fmt.Sprintf("%s%s", dir, "/examples/data/a.text"), nil)
	ctx.Response.Attachment("text_a.text", body)

	body, _ = web.ReadFile(fmt.Sprintf("%s%s", dir, "/examples/data/b.text"), nil)
	ctx.Response.Attachment("text_b.text", body)

	body, _ = web.ReadFile(fmt.Sprintf("%s%s", dir, "/examples/data/c.text"), nil)
	ctx.Response.Inline("text_c.text", body)

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerHelloForUploadFiles(ctx *web.Context) error {
	fmt.Println("HELLO I'M THE HELLO HANDER FOR UPLOAD FILES")

	fmt.Printf("\nAttachments: %+v\n", ctx.Request.FormData)
	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \""+ctx.Request.UrlParams["name"][0]+"\" }"),
	)
}

func HandlerFormDataForGet(ctx *web.Context) error {
	fmt.Println("HANDLING FORM DATA FOR GET")

	fmt.Printf("\nreceived")
	fmt.Printf("\nvar_one: %s", ctx.Request.GetFormDataString("var_one"))
	fmt.Printf("\nvar_two: %s", ctx.Request.GetFormDataString("var_two"))

	ctx.Response.SetFormData("var_one", "one")
	ctx.Response.SetFormData("var_two", "2")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \"form-data\" }"),
	)
}

func HandlerUrlFormDataForGet(ctx *web.Context) error {
	fmt.Println("HANDLING URL FORM DATA FOR GET")

	fmt.Printf("\nreceived")
	fmt.Printf("\nvar_one: %s", ctx.Request.GetFormDataString("var_one"))
	fmt.Printf("\nvar_two: %s", ctx.Request.GetFormDataString("var_two"))

	ctx.Response.SetFormData("var_one", "one")
	ctx.Response.SetFormData("var_two", "2")

	return ctx.Response.Bytes(
		web.StatusOK,
		web.ContentTypeApplicationJSON,
		[]byte("{ \"welcome\": \"form-data\" }"),
	)
}

Client

func main() {
	// create a new client
	c, err := web.NewClient(web.WithClientMultiAttachmentMode(web.MultiAttachmentModeBoundary))
	if err != nil {
		panic(err)
	}

	requestGet(c)
	requestPost(c)

	requestGetBoundary(c)

	requestAuthBasic(c)
	requestAuthJwt(c)

	requestOptionsOK(c)
	requestOptionsNotFound(c)

	bindFormData(c)
	bindUrlFormData(c)
}

func requestGet(c *web.Client) {
	request, err := c.NewRequest(web.MethodGet, "localhost:9001/hello/joao?a=1&b=2&c=1,2,3", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v", response)
}

func requestPost(c *web.Client) {
	request, err := c.NewRequest(web.MethodPost, "localhost:9001/hello/joao?a=1&b=2&c=1,2,3", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	data := struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}{
		Name: "joao",
		Age:  30,
	}

	bytes, _ := json.Marshal(data)

	response, err := request.WithBody(bytes).Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v\n", string(response.Body))
}

func requestGetBoundary(c *web.Client) {
	request, err := c.NewRequest(web.MethodGet, "localhost:9001/hello/joao/download", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("%+v", response)
}

func requestOptionsOK(c *web.Client) {
	request, err := c.NewRequest(web.MethodOptions, "localhost:9001/auth-basic", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	_, err = request.WithAuthBasic("joao", "ribeiro")
	if err != nil {
		panic(err)
	}

	request.SetHeader(web.HeaderAccessControlRequestMethod, []string{string(web.MethodGet)})
	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("\n\n%d: %s\n\n", response.Status, string(response.Body))
}

func requestOptionsNotFound(c *web.Client) {
	request, err := c.NewRequest(web.MethodOptions, "localhost:9001/auth-basic-invalid", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	_, err = request.WithAuthBasic("joao", "ribeiro")
	if err != nil {
		panic(err)
	}

	request.SetHeader(web.HeaderAccessControlRequestMethod, []string{string(web.MethodGet)})
	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("\n\n%d: %s\n\n", response.Status, string(response.Body))
}

func requestAuthBasic(c *web.Client) {
	request, err := c.NewRequest(web.MethodGet, "localhost:9001/auth-basic", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	_, err = request.WithAuthBasic("joao", "ribeiro")
	if err != nil {
		panic(err)
	}

	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("\n\n%d: %s\n\n", response.Status, string(response.Body))
}

func requestAuthJwt(c *web.Client) {
	request, err := c.NewRequest(web.MethodGet, "localhost:9001/auth-jwt", web.ContentTypeApplicationJSON, nil)
	if err != nil {
		panic(err)
	}

	_, err = request.WithAuthJwt(jwt.Claims{"name": "joao", "age": 30}, "bananas")
	if err != nil {
		panic(err)
	}

	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	fmt.Printf("\n\n%d: %s\n\n", response.Status, string(response.Body))
}

func bindFormData(c *web.Client) {
	request, err := c.NewRequest(web.MethodGet, "localhost:9001/form-data", web.ContentTypeMultipartFormData, nil)
	if err != nil {
		panic(err)
	}

	request.SetFormData("var_one", "one")
	request.SetFormData("var_two", "2")

	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	formData := struct {
		VarOne string `json:"var_one"`
		VarTwo int    `json:"var_two"`
	}{}

	if err := response.BindFormData(&formData); err != nil {
		fmt.Println(err)
	}

	fmt.Printf("\nvar_one: %s", response.GetFormDataString("var_one"))
	fmt.Printf("\nvar_two: %s", response.GetFormDataString("var_two"))

	fmt.Printf("\n\nFORM DATA: %+v\n", formData)
}

func bindUrlFormData(c *web.Client) {
	request, err := c.NewRequest(web.MethodGet, "localhost:9001/url-form-data", web.ContentTypeApplicationForm, nil)
	if err != nil {
		panic(err)
	}

	request.SetFormData("var_one", "one")
	request.SetFormData("var_two", "2")

	response, err := request.Send()
	if err != nil {
		panic(err)
	}

	formData := struct {
		VarOne string `json:"var_one"`
		VarTwo int    `json:"var_two"`
	}{}

	if err := response.BindFormData(&formData); err != nil {
		fmt.Println(err)
	}

	fmt.Printf("\nvar_one: %s", response.GetFormDataString("var_one"))
	fmt.Printf("\nvar_two: %s", response.GetFormDataString("var_two"))

	fmt.Printf("\n\nURL FORM DATA: %+v\n", formData)
}

Known issues

Follow me at

Facebook: https://www.facebook.com/joaosoft

LinkedIn: https://www.linkedin.com/in/jo%C3%A3o-ribeiro-b2775438/

If you have something to add, please let me know [email protected]