I am willing to create a sample http.Response instance in golang with a sample body string.
Problem is, its body property accepts ReadCloser instance. But as its a dummy response instance, I was wondering if there is some trick to set it easily without setting up all that stream read/close parts.
As suggested by Not_a_Golfer and JimB:
io.ReadCloser is an interface that is satisfied when a struct implements both the Read and the Close functions.
Fortunately, there is ioutil.NopCloser, which takes a io.Reader and wraps it in the nopCloser struct, which implements both Read and Close. However, its Close function does nothing as implied from the name.
Here is an example:
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
t := http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString("Hello World")),
}
buff := bytes.NewBuffer(nil)
t.Write(buff)
fmt.Println(buff)
}
To play with the code, click here.
Further to the top answer, I have found that in order for the response to be treated as the genuine article by clients, it needs to be more fully formed. For a normal (200) response, I do the following:
body := "Hello world"
t := &http.Response{
Status: "200 OK",
StatusCode: 200,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Body: ioutil.NopCloser(bytes.NewBufferString(body)),
ContentLength: int64(len(body)),
Request: req,
Header: make(http.Header, 0),
}
Then you can, for example, add headers (with a 401 status code, to ask for authorisation, say). req is the http.Request for which you are generating the response.
This should work..
func main(){
go serveHTTP(*port, *host)
select {}
}
func serveHTTP(port int, host string) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
requestHandler(w, r)
})
addr := fmt.Sprintf("%v:%d", host, port)
server := &http.Server {
Addr: addr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
err := server.ListenAndServe()
log.Println(err.Error())
}
func requestHandler(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, `Success!`)
}
Yes, the ioutil.NopCloser is just what I needed!
Am trying to test a method that performs calls to the facebook API (via a helper function) for a social-connect endpoint, and I want to mock the facebook response coming from the helper function, so my solution is like follows:
Expected facebook response (converted to my own UserData struct) is:
UserData {
ID: facebookID,
Email: email,
FirstName: firstName,
LastName: lastName,
}
So I create the expected response like this:
fbUserData, _ := json.Marshal(UserData{
ID: facebookID,
Email: email,
FirstName: firstName,
LastName: lastName,
})
fbUserDataResponse := &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(string(fbUserData))),
}
Then I can mock the response for the method calling the facebook API like this:
s.fbGateway.EXPECT().ExecuteGetQuery(userUrl).Return(fbUserDataResponse, nil).Times(1)
The point here is that this is really about mocking any kind of functions that return *http.Response data (in my case I am calling the facebook API via a helper function that returns the http Response, as mentioned above).
Related
I am developing an application in Go which uses the net/http package to make a number of external http requests/calls which I would like to monitor the http calls' latency, response times, status code etc.
As there are a number of endpoints which my applications calls, and there could be more that will be added in the future, I would like to consider a solution which will obtain the metrics for all such external http calls.
Any suggestions, guidance or examples on this is much appreciated.
I am thinking of some way in which a middleware/wrapper is added onto the Do/Raw call within the net/http package which I can create and modify a copy of but I'm not exactly sure how to do that.
Here's a very (!) basic example that abstracts http.Client's Do method with a Prometheus Counter that Inc's on every Do and labels the Counter with the host's name, the request method and the response code:
package main
import (
"log"
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Client struct {
Client *http.Client
Counter *prometheus.CounterVec
}
func NewClient(counter *prometheus.CounterVec) *Client {
return &Client{
Client: &http.Client{},
Counter: counter,
}
}
func (c *Client) Do(rqst *http.Request) (*http.Response, error) {
resp, err := c.Client.Do(rqst)
host := rqst.URL.Host
code := strconv.Itoa(resp.StatusCode)
c.Counter.With(prometheus.Labels{
"host": host,
"response_code": code,
}).Inc()
return resp, err
}
func main() {
metric := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "foo",
Help: "Number of HTTP requests.",
},
[]string{
"host",
"response_code",
},
)
prometheus.MustRegister(metric)
c := NewClient(metric)
go func() {
for {
method := http.MethodPost
url := "https://httpbin.org/post"
rqst, _ := http.NewRequest(method, url, nil)
_, _ = c.Do(rqst)
time.Sleep(15 * time.Second)
}
}()
log.Println("Starting server")
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
And:
# HELP foo Number of HTTP requests.
# TYPE foo counter
foo{host="httpbin.org",method="POST",response_code="200"} 6```
Problem description:
I am learning Golang to implement the REST API for a small project.
I was following this small example to get the some idea how to connect things.
However, it looks like there are some bugs in the sample example, that i could not get the expected response in postman after hitting the endpoints.
I have fixed it by adding the missing functions (HandleFunc functions) to make it work.
Problem Description:
However, I still have an issue with CreateEvent section.
The expectation is that after using POST method with a given sample Event (json format) like below, event list is updated.
{
"id": "23",
"title": "This is simple Go lang title for test!",
"Description":"In this course you will learn REST api implementation in Go lang"
}
But after reaching the "http://localhost:8080/events" endpoint in which I defined to return all the events (1 defined inside code, the other should be added by calling CreateEvent function) i get only one of the event (hard coded one inside code only) in response.
Here is the complete code.
I appreciate for any suggestions/comments.
package main
import (
"fmt"
"log"
"net/http"
"io/ioutil"
"encoding/json"
"github.com/gorilla/mux"
)
func homeLink(w http.ResponseWriter, r *http.Request) {
fmt.Println("test started!")
fmt.Fprintf(w, "Welcome home!")
}
func main() {
router := mux.NewRouter().StrictSlash(true)
router.HandleFunc("/", homeLink)
/*i have added the next 3 lines, missing in the sample code*/
router.HandleFunc("/event", createEvent)
router.HandleFunc("/events/{id}", getOneEvent)
router.HandleFunc("/events", getAllEvents)
log.Fatal(http.ListenAndServe(":8080", router))
}
type event struct {
ID string `json:"ID"`
Title string `json:"Title"`
Description string `json:"Description"`
}
type allEvents []event
var events = allEvents{
{
ID: "1",
Title: "Introduction to Golang",
Description: "Come join us for a chance to learn how golang works and get to eventually try it out",
},
}
func createEvent(w http.ResponseWriter, r *http.Request) {
var newEvent event
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Fprintf(w, "Kindly enter data with the event title and description only in order to update")
}
fmt.Println("Create Event is called!")
json.Unmarshal(reqBody, &newEvent)
events = append(events, newEvent)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newEvent)
}
func getOneEvent(w http.ResponseWriter, r *http.Request) {
eventID := mux.Vars(r)["id"]
fmt.Println("get one event is called!")
fmt.Println(eventID)
for _, singleEvent := range events {
if singleEvent.ID == eventID {
json.NewEncoder(w).Encode(singleEvent)
}
}
}
func getAllEvents(w http.ResponseWriter, r *http.Request) {
fmt.Println("Get all events is called!")
json.NewEncoder(w).Encode(events)
}
Your code is working fine. I have tested it (just copied above code and ran in my local machine and tested with Postman).
Btw, i have added few recommendations for a better code below.
If there is not nil error, handle it and return.
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Fprintf(w, "Kindly enter data with the event title and description only in order to update")
return //add this return, otherwise continue the function with the error
}
Put this json error handling for createEvent Handler function
err = json.Unmarshal(reqBody, &newEvent)
if err != nil {
fmt.Fprintf(w, "json format invalid")
return
}
Add http methods to your endpoints.
router.HandleFunc("/", homeLink).Methods(http.MethodGet)
/*i have added the next 3 lines, missing in the sample code*/
router.HandleFunc("/event", createEvent).Methods(http.MethodPost)
router.HandleFunc("/events/{id}", getOneEvent).Methods(http.MethodGet)
router.HandleFunc("/events", getAllEvents).Methods(http.MethodGet)
I have two tasks I need to fulfill when the "/" pattern is present in a request, both of which require using http handlers.
They are:
http.Handle("/", http.FileServer(http.Dir("dtfw-tool/build/")))
http.HandleFunc("/", index)
The index handler checks for proper authentication to access a webpage, and the handler above it serves up a directory (in the future I will make it to where it will only serve the directory if authentication requirements are met).
Is it possible to have two handlers for the same pattern (currently gives error)? If not, is there any other way to check authentication and serve up the directory with a single handler?
Create a middleware to authenticate users and return the handler to main Handle which will wrap your final handler
package main
import (
"log"
"net/http"
)
func main() {
finalHandler := http.HandlerFunc(final)
http.Handle("/", authentication(finalHandler))
http.ListenAndServe(":3000", nil)
}
func authentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Executing authentication")
next.ServeHTTP(w, r) //`next.ServeHTTP(w, r)` will forward the request and response to next handler.
})
}
func final(w http.ResponseWriter, r *http.Request) {
log.Println("Executing finalHandler")
w.Write([]byte("User authenticated"))
}
In Golang HanlderFunc is used to return hanlder which will become a middlware to wrap the main function:
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
It is also defined in the source code for server.go
Playground Example
I'm going to have to retract my previous answer acceptance because this is much closer to what I was looking for (have to import github.com/abbot/go-http-auth):
package main
import (
"fmt"
"net/http"
auth "github.com/abbot/go-http-auth"
)
func Secret(user, realm string) string {
if user == "john" {
// password is "hello"
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
}
return ""
}
func main() {
fmt.Println("-----> Starting HTTP server...")
authenticator := auth.NewBasicAuthenticator("secret.com", Secret)
http.HandleFunc("/", authenticator.Wrap(func(res http.ResponseWriter, req *auth.AuthenticatedRequest) {
http.FileServer(http.Dir(".")).ServeHTTP(res, &req.Request)
}))
http.ListenAndServe(":5042", nil)
}
This method is much easier to follow and more intutive (for me at least).
I am trying to turn off handling GET requests in golang.
I just want to handle POST.
Is it possible to do?
Reason for doing so is that i can see more and more memory being allocated by golang whenever i go to localhost:8080 and refresh page multiple times.
Here is my test code:
package main
import (
"fmt"
"net/http"
"encoding/json"
)
type test_struct struct {
Test string
}
var t test_struct
func handlePOST(rw http.ResponseWriter, req *http.Request) {
switch req.Method {
case "POST":
decoder := json.NewDecoder(req.Body)
decoder.Decode(&t)
defer req.Body.Close()
fmt.Println(t.Test)
}
}
func main() {
http.HandleFunc("/", handlePOST)
http.ListenAndServe(":8080", nil)
}
You cannot not handle GET requests, Go's HTTP server (or rather its http.ServeMux) only allows you to specify a path pattern before dispatching the request to your handler. HTTP method related routing can only happen at the handler level.
Note that some external mux libraries allow you to register handlers to specific HTTP methods only, but the decision and routing based on that also happens in "hidden" handlers of those libraries.
What you're doing is the best: simply do nothing in the handler if the HTTP method is not the one you intend to handle, or even better: send back a http.StatusMethodNotAllowed error response:
func myHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
return
}
var t test_struct // Use local var not global, else it's a data race
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&t); err != nil {
fmt.Println("Error decoding:", err)
}
fmt.Println(t.Test)
}
I am using Jon Calhoun's Go MVC framework from github.
The framework uses julienschmidt/httprouter as its only dependency.
I have a similar main method as found in the example:
func main() {
//register routes
router := httprouter.New()
//default
router.GET("/", controllers.Login.Perform(controllers.Login.Index))
//login
router.GET("/login", controllers.Login.Perform(controllers.Login.Login))
router.POST("/login", controllers.Login.Perform(controllers.Login.PostLogin))
//dashboard
router.GET("/dashboard", controllers.Dashboard.Perform(controllers.Dashboard.Index))
//listen and handle requests
log.Fatal(http.ListenAndServe(":"+helpers.ReadConfig("port_http"), router))
}
I make a post to the login url, and it calls the following method:
func (self LoginController) PostLogin(w http.ResponseWriter, r *http.Request, ps httprouter.Params) error {
//create our api url
var url = helpers.ReadConfig("api") + "login"
//fill model to post
login := models.LoginModel{
Password: r.FormValue("password"),
Email: r.FormValue("username"),
}
//render json from model
bytes, err := json.Marshal(login)
if err != nil {
panic(err)
}
//post to the API helpers
var resp = helpers.ApiPost(url, r, string(bytes))
//check response if successful
if resp.Code != constants.ApiResp_Success {
//TODO: Handle API Errors
login.Password = ""
errors := make(map[int]string)
errors[1] = "Please provide valid credntials."
login.Common = models.CommonModel{
ErrorList: errors,
}
return views.Login.Index.Render(w, login, helpers.AcceptsGzip(r))
}
log.Println("---Redirect--")
http.Redirect(w, r, "/dashboard", 307)
log.Println("-----")
return views.Dashboard.Index.Render(w, login, helpers.AcceptsGzip(r))
}
Basically, if the login was not correct I return the same view. If the login is correct I want to redirect to another method in a different controller.
However when I call http.Redirect(w, r, "/dashboard", 307), it returns the following error:
http: multiple response.WriteHeader calls
I'm not sure exactly why this is happening, but I suspect that it has something to do with my listener calling the Perform function, which creates a http.handler, as shown below.
func (c *Controller) Perform(a Action) httprouter.Handle {
return httprouter.Handle(
func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
//set response headers
//TODO: set appropriate responce headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Cache-Control", "public, max-age=0")
w.Header().Set("Token", "NOT-A-VALID-TOKEN")
w.WriteHeader(200)
if err := a(w, r, ps); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
}
Does anyone have any idea how to redirect using this MVC framework? Or have a one off solution?
http.ResponseWriter's WriteHeader method can only be called once per HTTP response, for obvious reasons: You can only have a single response code, and you can only send the headers once.
The error you see means that it is called a second time on the same response.
Your middleware calls:
w.WriteHeader(200)
Then your handler also calls:
http.Redirect(w, r, "/dashboard", 307)
log.Println("-----")
return views.Dashboard.Index.Render(w, login, helpers.AcceptsGzip(r))
Your middleware should never call WriteHeader, until after the fate of the response is known.
Further, without knowing about your particular MVC framework, it seems possible that after you send the 307 status, then you also tell the MVC framework to render a response, which may also call WriteHeader again.