Skip to content

Latest commit

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Convert Go func to http.HandleFunc that handle json request and response json

New Status Code Error

funcNewStatusCodeError(codeint, innerErrorerror) (errerror)

NewStatusCodeError for returning an error with http code

To Handler Func

funcToHandlerFunc(funcs...interface{}) http.HandlerFunc

ToHandlerFunc convert any go func to a http.HandleFunc, that will accept json.Unmarshal request body as parameters, and response with a body with a return values into json.

The second argument is an arguments injector, it's parameter should be (w http.ResponseWriter, r *http.Request), and return values Will be injected to first func's first few arguments.

1) Simple types

varhelloworld=func(namestring, genderint) (rstring, errerror) {
ifgender==1 {
r=fmt.Sprintf("Hi, Mr. %s", name)
} elseifgender==2 {
r=fmt.Sprintf("Hi, Mrs. %s", name)
} else {
err=fmt.Errorf("Sorry, I don't know about your gender.")
}
return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
responseBody:=httpPostJSON(hf, ` {"params": [ "Gates", 1 ]}	`)
fmt.Println(responseBody)
responseBody=httpPostJSON(hf, ` {"params": [ "Gates", 2 ]}	`)
fmt.Println(responseBody)
responseBody=httpPostJSON(hf, ` {"params": [ "Gates", 3 ]}	`)
fmt.Println(responseBody)
//Output:// {"results":["Hi, Mr. Gates",null]}//// {"results":["Hi, Mrs. Gates",null]}//// {"results":["",{"error":"Sorry, I don't know about your gender.","value":{}}]}

2) More complicated types

varhelloworld=func(namestring, pstruct {
NamestringAddressstruct {
ZipcodeintAddress1string
}
}) (rstring, errerror) {
r=fmt.Sprintf("Hi, Mr. %s, Your zipcode is %d", name, p.Address.Zipcode)
return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
responseBody:=httpPostJSON(hf, ` {"params": [ "Felix", { "Address": { "Zipcode": 100 } } ]}	`)
fmt.Println(responseBody)
//Output:// {"results":["Hi, Mr. Felix, Your zipcode is 100",null]}

3) Slice, maps, pointers

varhelloworld=func(
names []string,
genderOfNamesmap[string]string,
p*struct {
Names []stringAddressstruct {
ZipcodeintAddress1string
}
},
pointerNames*[]string,
) (rstring, errerror) {
r=fmt.Sprintf("Hi, Mr. %s, Your zipcode is %d, Your gender is %s", names[0], p.Address.Zipcode, genderOfNames[names[0]])
return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
responseBody:=httpPostJSON(hf, `{"params":[ ["Felix"] ]}`)
fmt.Println(responseBody)
responseBody=httpPostJSON(hf, ` {"params": [ ["Felix", "Gates"], { "Felix": "Male", "Gates": "Male" }, { "Names": ["F1", "F2"], "Address": { "Zipcode": 100 } }, ["p1", "p2"] ]}	`)
fmt.Println(responseBody)
responseBody=httpPostJSON(hf, ``)
fmt.Println(responseBody)
//Output:// {"results":["",{"error":"require 4 params, but passed in 1 params","value":{}}]}//// {"results":["Hi, Mr. Felix, Your zipcode is 100, Your gender is Male",null]}//// {"results":["",{"error":"decode request params error","value":{}}]}

4) First context: If first parameter is a context.Context, It will be passed in with request.Context()

varhelloworld=func(ctx context.Context, namestring) (rstring, errerror) {
userid:=ctx.Value("userid").(string)
r=fmt.Sprintf("Hello %s, My user id is %s", name, userid)
return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
middleware:=func(inner http.HandlerFunc) http.HandlerFunc {
returnfunc(w http.ResponseWriter, r*http.Request) {
r=r.WithContext(context.WithValue(r.Context(), "userid", "123"))
inner(w, r)
}
}
responseBody:=httpPostJSON(middleware(hf), `{"params": [ "Hello" ]}`)
fmt.Println(responseBody)
//Output:// {"results":["Hello Hello, My user id is 123",null]}

5) Errors handling with details in returned json

varhelloworld=func(namestring, genderint) (rstring, errerror) {
err=&complicatedError{ErrorCode: 8800, ErrorDeepReason: "It crashed."}
return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
responseBody:=httpPostJSON(hf, ` {"params": [ "Gates", 1 ]}	`)
fmt.Println(responseBody)
//Output:// {"results":["",{"error":"It crashed.","value":{"ErrorCode":8800,"ErrorDeepReason":"It crashed."}}]}

6) Can use get with empty body to fetch the handler

varhelloworld=func(ctx context.Context) (rstring, errerror) {
r="Done"return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
ts:=httptest.NewServer(hf)
deferts.Close()
res, err:=http.Get(ts.URL)
iferr!=nil {
log.Fatal(err)
}
b, _:=ioutil.ReadAll(res.Body)
res.Body.Close()
fmt.Println(string(b))
//Output:// {"results":["Done",null]}

7) Use NewStatusCodeError or implement StatusCodeError interface to set http status code of response.

varhelloworld=func(namestring, genderint) (rstring, errerror) {
err=jsonhandlerfunc.NewStatusCodeError(http.StatusForbidden, fmt.Errorf("you can't access it"))
return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld)
responseBody, code:=httpPostJSONReturnCode(hf, ` {"params": [ "Gates", 1 ]}	`)
fmt.Println(code)
fmt.Println(responseBody)
//Output:// 403// {"results":["",{"error":"you can't access it","value":{}}]}

8) Pass in another injector func to get arguments from *http.Request and pass it to first func.

the argument injector parameters should be func(w http.ResponseWriter, r *http.Request) the return values except the last error will be passed to the first func.

varhelloworld=func(cartIdint, userIdstring, namestring, genderint) (rstring, errerror) {
r=fmt.Sprintf("cardId: %d, userId: %s, name: %s, gender: %d", cartId, userId, name, gender)
return
}
varargsInjector=func(w http.ResponseWriter, r*http.Request) (cartIdint, userIdstring, errerror) {
cartId=20userId="100"return
}
hf:=jsonhandlerfunc.ToHandlerFunc(helloworld, argsInjector)
responseBody, code:=httpPostJSONReturnCode(hf, ` {"params": [ "Gates", 2 ]}	`)
fmt.Println(code)
fmt.Println(responseBody)
varargsInjectorWithError=func(w http.ResponseWriter, r*http.Request) (cartIdint, userIdstring, errerror) {
err=jsonhandlerfunc.NewStatusCodeError(http.StatusForbidden, fmt.Errorf("you can't access it"))
return
}
hf=jsonhandlerfunc.ToHandlerFunc(helloworld, argsInjectorWithError)
responseBody, code=httpPostJSONReturnCode(hf, ` {"params": [ "Gates", 2 ]}	`)
fmt.Println(code)
fmt.Println(responseBody)
// You can pass more injectors to addup provide arguments from beginning.varcardItInjector=func(w http.ResponseWriter, r*http.Request) (cartIdint, errerror) {
cartId=30return
}
varuserIdInjecter=func(w http.ResponseWriter, r*http.Request) (userIdstring, errerror) {
userId="300"return
}
hf=jsonhandlerfunc.ToHandlerFunc(helloworld, cardItInjector, userIdInjecter)
responseBody, code=httpPostJSONReturnCode(hf, ` {"params": [ "Gates", 2 ]}	`)
fmt.Println(code)
fmt.Println(responseBody)
// You can also pass only one injector without main funchf=jsonhandlerfunc.ToHandlerFunc(cardItInjector)
responseBody, code=httpPostJSONReturnCode(hf, "")
fmt.Println(code)
fmt.Println(responseBody)
//Output:// 200// {"results":["cardId: 20, userId: 100, name: Gates, gender: 2",null]}//// 403// {"results":["",{"error":"you can't access it","value":{}}]}//// 200// {"results":["cardId: 30, userId: 300, name: Gates, gender: 2",null]}//// 200// {"results":[30,null]}

9) panic if injectors type not match

deferfunc() {
ifr:=recover(); r!=nil {
fmt.Println(r)
}
}()
varinj=func(w http.ResponseWriter, r*http.Request) (a*http.Request, bfloat64, cstring, errerror) {
return
}
varf=func(a, b, cstring) (errerror) {
return
}
jsonhandlerfunc.ToHandlerFunc(f, inj)
fmt.Println("DONE")
//Output:// func(string, string, string) error params type is [string string string], but injecting [*http.Request float64 string]

10) Config ErrHandler

varconfidentialErr=fmt.Errorf("Internal error, contains confidential information, should not exposed")
varerrMapping=map[error]error{
confidentialErr: errors.New("system error"),
}
cfg:=&jsonhandlerfunc.Config{
ErrHandler: func(oldErrerror) (newErrerror) {
returnerrMapping[oldErr]
},
}
varhelloworld=func(namestring, genderint) (rstring, errerror) {
err=confidentialErrreturn
}
hf:=cfg.ToHandlerFunc(helloworld)
responseBody:=httpPostJSON(hf, ` {"params": [ "Gates", 1 ]}	`)
fmt.Println(responseBody)
//Output:// {"results":["",{"error":"system error","value":{}}]}

Type: Config

typeConfigstruct {
ErrHandlerfunc(oldErrerror) (newErrerror)
}

Config: To Handler Func

func (cfg*Config) ToHandlerFunc(funcs...interface{}) http.HandlerFunc

Type: Req

typeReqstruct {
Paramsinterface{} `json:"params"`
}

Type: Resp

typeRespstruct {
Resultsinterface{} `json:"results"`
}

Type: Response Error

typeResponseErrorstruct {
Errorstring`json:"error,omitempty"`Valueinterface{} `json:"value,omitempty"`
}

ResponseError is error of the Go func return values will be wrapped with this struct, So that error details can be exposed as json.

Type: Status Code Error

typeStatusCodeErrorinterface {
StatusCode() int
}

StatusCodeError for the error you returned contains a StatusCode method, It will be set to to http response.

About

Convert Go func to http.HandleFunc that handle json request and response json

Resources

Stars

6 stars

Watchers

39 watching

Forks

Releases

Packages

Used by

Contributors

Languages