webapi

package module
v0.0.0-...-7285f2c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 11, 2023 License: MIT Imports: 14 Imported by: 0

README

WebAPI

 _       ____  ___    __    ___   _  
\ \    /| |_  | |_)  / /\  | |_) | | 
 \_\/\/ |_|__ |_|_) /_/--\ |_|   |_| 
 

Build Status Go Report Card Open Source Helpers

中文 | English

WebAPI is a basic library for Web API service development for Golang. Using WebAPI can effectively reduce the probability of coding errors and avoid boring duplicate code. WebAPI for Golang is inspired by Microsoft ASP.NET Core, so if you are familiar with .NET or JavaEE developers, you will be well equipped.

What & How

Auto-Registration & Composite Controller

Focusing on business core avoidance of irrelevant rules, DDD is highly compatible with design patterns such as MVVM. Different business modules have their own controller(s), which support the design of module subdivision and multi-module unified integration (allowing multi-person collaboration). Conventional automatic route registration reduces redundant code, improves design efficiency while avoiding the possibility of loss of synchronization between the public address and the internal implementation.

Declare the controller:

type Article struct {
	webapi.Controller
}

Declare the endpoint:

func (article *Article) Show(query struct {
  GUID string `json:"guid"`
}) string {
	return fmt.Sprintf("you are reading post-%s", query.GUID)
}

WebAPI follows the Golang principle, any accessible method (a function that begins with an uppercase letter) is registered as an API endpoint.

The endpoint will be registered as /article/show?guid=[guid]. If there are multiple controllers handling different services, the controller alias can be specified by the api tag on any member of Controller:

type article struct {
	webapi.Controller `api:"article"`
	id uint
}

Then both methods from article and Article will be registered under /article. However, it should be noted that such registrars need to avoid duplicate names. However, at runtime the WebAPI won't make a fatal warning(panic) for the duplicate name method.

When you use it, you can divide each business module into different people to complete. Finally, they can be easily integrated through api tag.

Query/Body Auto-Serialization Support

Complete elimination of text reading, serialization/deserialization, query retrieval and transformation in business logic, and even middleware, you can even configure non-system built-in/private sequencers such as MsgPack.

Try to use curl to access the API just now:

~ curl http://localhost:9527/article/show\?guid\=79526
#you are reading post-79526

You can see that the parameters are automatically placed in query. It also supports automatic text serialization, such as declaration methods:

func (article *article) Save(entity *struct {
	conf       struct{} `api:"save"`
	ID         uint
	Title      string
	Content    string
	CreateTime time.Time
}, query struct {
	CreateTime string `json:"time"`
}) {
	entity.CreateTime, _ = time.Parse("2006-01-02", query.CreateTime)
	entity.ID = article.id
	article.Reply(http.StatusAccepted, entity)
}

This method will be registered as [POST] /article/{digits}/save, because there is a *struct{} structure, so the default method is POST. However, the HTTP methods can be explicitly declared in the form of options tag on any member of structure. Also use curl:

~ curl -X "POST" "http://localhost:9527/article/123/save?time=2019-01-01" \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{
  "Title": "Hello WebAPI for Golang",
  "Content": "Awesome!"
}'

#{"ID":123,"Title":"Hello WebAPI for Golang","Content":"Awesome!","CreateTime":"2019-01-01T00:00:00Z"}

You can see that the time in the query was successfully accessed and assigned to the body. Please also note that the previously used string as the return value, but this node manually reply to the client using .Reply(STATUSCODE, INTERFACE{}) to automatically process object.

The serializer can be specified manually with Serializer property in the Context.

At the same time, the query and body structure support check, add the Check() error method to them to check the legality of the data before entering the business code, and isolate the defense code from the business.

In the latest version of the code we have added semantics support, simply configure semantics at the Controller level to enable semantic registration points, for example if the method name above is changed to PutSave it will become a Save registration point for the PUT method.

Routing Precondition (Pre-Parameterized Access) Support

The convergence controller handles the data category and sets up API access barriers. Provides matching-fallback and specific service controller pre-conditions that other routing services cannot provide, which isolates illegal access, reduces the probability of errors, improves service coding efficiency, and improves system robustness.

As you saw in the previous request, 123 in the access address /article/123/save was caught and finally appeared in the ID of the reply body. WebAPI allows preconditions (Precondition) to be set for the controller, the declared method:

func (article *article) Init(id uint) (err error) {
	article.id = id
	return
}

You only need to declare a method with a return value of error and the name Init for the controller to automatically call it before entering the actual method. There have also been some changes to the node registration form. If the parameter is

  • Integer, long integer, unsigned integer, unsigned long integer (Int/UInt) will get a registration point for /{digits}
  • Single precision floating point, double precision floating point (Float32/64) then will get a registration point of /{float}
  • String will get a registration point for /{string}

So the above Init(uint) error function will generate a registration point for /{digits}.

If the error value returned by the calling function is not empty, the client Bad Request will be notified. This distinguishes between the Object Method and the Static Method from the side. When coding, you can pay more attention to the business itself without worrying about various preconditions, or saving a lot of approximate code.

Endpoint Condition (Post-Parameterized Access) Support

No reverse proxy required to configure pseudo-static to provide native support for parameterized access, providing a more intuitive and concise API.

Since the pre-parameterized access is supported, then natural parameterized access is also possible. Just now we encountered that access to the body of the article requires the use of query parameters, although it can work normally, but it seems too monotonous. Supporting the use of pre-parameters requires a method like Read, which feels unnatural. We can provide access forms like /article/{guid} in the form of post-parameters:

func (article *Article) Index(guid string) string {
	return fmt.Sprintf("you are reading post-%s", guid)
}

Test with curl:

~ curl http://localhost:9527/article/id-233666
#you are reading post-id-233666

⚠️ Attention

This method can also be implemented by the method of func (article *article) Index(id int). In this case, the two methods allow coexistence because the former is /article/{string} and the latter is /article/{digits}. Be aware of such issues when collaborating. If a duplicate registration node occurs, the controller will fail to register and prompt an error.

Performance

In a test environment of 8 vCPU / 16G RAM TLinux virtual machine (not idle) via the Cyborg performance utility from 100 clients, 200 requests to initiate pressure to 580 client, 1160 requested Hello World interface performance record:

Clients Requests Total QPS
100 200 0.011942s 8374.09354
120 240 0.010043s 11949.1254
140 280 0.008378s 16710.2346
160 320 0.012892s 12410.937
180 360 0.011250s 15999.7355
200 400 0.011609s 17228.3244
220 440 0.017484s 12582.8826
240 480 0.020186s 11889.3559
260 520 1.004284s 258.890958
280 560 0.015453s 18119.6865
300 600 0.016319s 18383.022
320 640 1.011559s 316.343278
340 680 1.014014s 335.301017
360 720 0.016673s 21591.3134
380 760 0.020759s 18305.5029
400 800 1.007692s 396.946831
420 840 0.024735s 16980.1533
440 880 0.023144s 19011.1727
460 920 0.025437s 18083.5409
480 960 1.006551s 476.875941
500 1000 1.008704s 495.685546
520 1040 1.010670s 514.510124
540 1080 1.009176s 535.089889
560 1120 0.034008s 16466.9452
580 1160 0.031361s 18494.0912

Since the target machine and the pressure-initiating machine are both virtual machines, data jitter is presented. According to actual experience, this jitter does not exist on the physical server. The median overall performance was 12582.88259 (12k), and the median recording performance below 1000 was discarded. 16980.15331 (17k). Performance is relatively optimistic for a single node.


Finally, Welcome use WebAPI. Cool to Code.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Serializers = map[string]Serializer{
		"application/x-www-form-urlencoded": &formSerializer{},
		"application/json":                  &jsonSerializer{},
		"application/xml":                   &xmlSerializer{},
		"":                                  &jsonSerializer{},
	}
)

JSONSerializer JSON Serializer

Functions

This section is empty.

Types

type Config

type Config struct {
	//UseLowerLetter Use lower letter in path
	UseLowerLetter bool

	//AliasTagName Replace the system rule name with the provided name, default is "api"
	AliasTagName string

	//HTTPMethodTagName Specify the specific method for the endpoint, default is "options"
	HTTPMethodTagName string

	//CustomisedPlaceholder Used to specify where the parameters should be in the URL. The specified string will quoted by {}.
	//E.G.: param -> {param}
	CustomisedPlaceholder string

	//AutoReport This option will display route table after successful registration
	DisableAutoReport bool
}

Config Configuration

type Context

type Context struct {
	Deserializer Serializer
	Serializer   Serializer

	BeforeReading func([]byte) []byte
	BeforeWriting func(int, []byte) []byte
	// contains filtered or unexported fields
}

Context HTTP Request Context

func (*Context) Body

func (ctx *Context) Body() []byte

Body The Body Bytes from Context

func (*Context) Context

func (ctx *Context) Context() *Context

Context Get Context

func (*Context) GetRequest

func (ctx *Context) GetRequest() *http.Request

GetRequest Get Request from Context

func (*Context) GetResponseWriter

func (ctx *Context) GetResponseWriter() ResponseWriter

GetResponseWriter Get ResponseWriter as io.Writer to support stream write

func (*Context) Redirect

func (ctx *Context) Redirect(addr string, httpstatus ...int)

Redirect Jump to antoher url

func (*Context) Reply

func (ctx *Context) Reply(httpstatus int, obj ...interface{}) (err error)

Reply Reply to client with any data which can be marshaled into bytes if not bytes or string

func (*Context) ResponseHeader

func (ctx *Context) ResponseHeader() http.Header

ResponseHeader Response Header

func (*Context) SetCookies

func (ctx *Context) SetCookies(cookies ...*http.Cookie)

SetCookies Set cookies

func (*Context) StatusCode

func (ctx *Context) StatusCode() int

StatusCode Context Status Code

func (*Context) Write

func (ctx *Context) Write(httpstatus int, data []byte) (err error)

Write Write to response(only for once)

type Controller

type Controller interface {
	Redirect(string, ...int)
	SetCookies(...*http.Cookie)
	Reply(int, ...interface{}) error
	Write(int, []byte) error
	ResponseHeader() http.Header
	Context() *Context
}

Controller Controller statement

type HTTPHandler

type HTTPHandler func(*Context)

HTTPHandler Public HTTP Handler

type Host

type Host struct {
	// contains filtered or unexported fields
}

Host Service for HTTP

func NewHost

func NewHost(conf Config, middlewares ...Middleware) (host *Host)

NewHost Create a new service host

func (*Host) AddEndpoint

func (host *Host) AddEndpoint(method string, path string, handler HTTPHandler, middlewares ...Middleware) (err error)

AddEndpoint Register the endpoint with the host

func (*Host) Errors

func (host *Host) Errors() []error

Errors Return server build time error

func (*Host) Group

func (host *Host) Group(basepath string, register func(), middlewares ...Middleware)

Group Set prefix to endpoints

func (*Host) Register

func (host *Host) Register(basepath string, controller Controller, middlewares ...Middleware) (err error)

Register Register the controller with the host

func (*Host) ServeHTTP

func (host *Host) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP service http request

func (*Host) Use

func (host *Host) Use(middlewares ...Middleware) *Host

Use Add middlewares into host

type LogService

type LogService interface {
	//Log with [datetime] prefix
	Log(tpl string, args ...interface{})

	//Write only text
	Write(tpl string, args ...interface{})

	//Stop exit
	Stop()
}

LogService Log service

type Middleware

type Middleware interface {
	Invoke(ctx *Context, next HTTPHandler)
}

Middleware Middleware

type Reply

type Reply struct {
	Status int
	Body   interface{}
}

Reply Default implementation of Response

func (Reply) Data

func (reply Reply) Data() interface{}

Data Body

func (Reply) StatusCode

func (reply Reply) StatusCode() int

StatusCode HTTP Status Code

type Replyable

type Replyable interface {
	StatusCode() int
	Data() interface{}
}

Replyable Replyable for request reply

type ResponseWriter

type ResponseWriter interface {
	Write(p []byte) (int, error)
	Header() http.Header
	WriteHeader(statusCode int)
}

ResponseWriter is a alternative for ResponseWriter Request

type Serializer

type Serializer interface {
	Marshal(interface{}) ([]byte, error)
	Unmarshal([]byte, interface{}) error
	ContentType() string
}

Serializer Serializer

type Validator

type Validator interface {
	Check() error
}

Validator Validator for body and query structures

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL