Why isn't the Response field of this HTTP Request being populated? - go

The comment for the field Response in the type http.Request is as follows.
// Response is the redirect response which caused this request
// to be created. This field is only populated during client
// redirects.
Response *Response
However, it seems to me that this field is not being populated during requests, as it is implied that it is. Consider the following example:
package main
import (
"net/http"
"log"
"fmt"
)
func handleA(writer http.ResponseWriter, request *http.Request) {
http.Redirect(writer, request, "/b", http.StatusSeeOther)
}
func handleB(writer http.ResponseWriter, request *http.Request) {
fmt.Println(request.Response)
}
func main() {
http.HandleFunc("/a", handleA)
http.HandleFunc("/b", handleB)
log.Fatal(http.ListenAndServe(":8080", nil))
}
If I compile and run this code and navigate to localhost:8080/a, then I get redirected to localhost:8080/b and the server prints <nil> to the console. But shouldn't it be printing out a non-nil value, since the request is coming as the result of a redirect?

In your sample, the redirect is happening in the browser; the server doesn't get to know what response generated the redirect. That field is populated when making HTTP requests from a Go app; for example, if you use http.Client to request a URL, and the response is a redirect, it generates a new Request for the redirect URL, and in that Request, the Response field will be populated with the response that triggered that redirect.
This is evidenced in the source for http.Client: https://golang.org/src/net/http/client.go#L669

Related

Request Body is always empty

I am sending a POST request using POSTMAN to a route in my localhost.
The route has been perfectly defined in my main.go file. Moreover, it also gets triggered by sending the post request.
However, when I try to print the body, it always seems to be empty.
I tried to replicated the issue with the code below
package main
import (
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"path"
"strings"
)
func MeetingOperations(w http.ResponseWriter, r *http.Request) {
reqBody, _ := ioutil.ReadAll(r.Body)
fmt.Fprintf(w, "%+v", string(reqBody))
fmt.Println(string(reqBody))
fmt.Println("SCHEDULE MEETING ROUTE")
}
func handleRequests() {
http.HandleFunc("/meetings/", MeetingOperations)
log.Fatal(http.ListenAndServe(":10000", nil))
}
func main() {
handleRequests()
}
Yet the body is logged as empty in the console.
I have tried sending a request using curl with no success. I also tried creating an HTML template form and submitting it with no luck.
Any sort of help or explanation is highly appreciated.
You have a trailing slash in your route definition, but in your Postman request its missing. If you use a library, you can typically handle this case.

HTTP client receives status code 200 when server panics with gin-gonic and gin-contrib/gzip

When accessing the gin-gonic server below, the HTTP client should receive the code 500, but receives the code 200.
package main
import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(gzip.Gzip(gzip.DefaultCompression))
r.POST("/test", func(c *gin.Context) {
panic("test") // Server panic and client should receive code 500.
})
r.Run(":8080")
}
When accessing /test from a HTTP client, the go server log is as below and looks return the code 500.
[GIN] 2020/09/28 - 10:23:14 | 500 | 67.2995ms | ::1 | POST "/test"
2020/09/28 10:23:14 [Recovery] 2020/09/28 - 10:23:14 panic recovered:
test
C:/path/to/myproject/main.go:16 (0x8f193f)
main.func1: panic("test")
But HTTP client receives the code 200.
When I remove r.Use(gzip.Gzip(gzip.DefaultCompression)), the HTTP client receives the code 500.
Why the client receives code 200 with r.Use(gzip.Gzip(gzip.DefaultCompression)), How can I fix this?
I've reproduce your case. Postman got code 200 but the server results 500 instead.
The server will call c.Next() to execute 4 handlers when receive post requests. The sequence is as follow:
gin.Logger
gin.Recovery
gzip.Gzip(gzip.DefaultCompression)
your handler
Here is gin responseWriter writes response header and it will write header only once.
func (w *responseWriter) WriteHeaderNow() {
if !w.Written() {
w.size = 0
w.ResponseWriter.WriteHeader(w.status)
}
}
Both gzip.Gzip(gzip.DefaultCompression) and gin.Recovery has defer func to write response header. Golang's deferred calls are executed in last-in-first-out order. So gzip.Gzip(gzip.DefaultCompression) will write response header to 200, and gin.Recovery won't write reponse header to 500 as expected.
So to solve this problem, you should change the order of handlers and make sure gin.Recovery is the last handler to load.
Adding the recovery middleware last seems to fix this.
package main
import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Logger())
r.Use(gzip.Gzip(gzip.DefaultCompression))
r.Use(gin.Recovery())
r.POST("/test", func(c *gin.Context) {
panic("test") // Server panic and client should receive code 500.
})
r.Run(":8080")
}

Golang removes double slashes and fires GET instead POST requests , how can I skip this in Cloud Functions?

I have a Cloud function where the same endpoint accepts 2 methods: POST and GET.
My problem is when the client tries to upload a multipart/form-data file through a POST request and by mistake the url contains double slashes, Golang redirects to GET method.
I have looked some replies where they talk about the Clean method https://golang.org/src/path/path.go?s=2443:2895#L74. And how the Mux under the hod is redirecting to GET.
Is there any way where I can check if that request has been redirected? so I can decide if the client has typed double slashes send a 400 Response for example instead the response from the logic in the GET method. I can't find that info in the headers. fmt.Printf("%+v", r)
Is there any way to skip the Clean method and accept the double slashes?.
Endpoint: https://google.com/hello/folder/folder//image.jpg
package test
func Hello(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
//some logic here
return
case "POST":
//some logic here
return
default:
fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.")
return
}
}
Thanks.

GoLang AWS Lambda Function Missing Body from API Gateway Request

I'm using an AWS Lambda function to handle a request from an AWS API Gateway call. I'm sending a payload on the request, and I can verify in CloudWatch that the payload is being passed from the gateway to the lambda function. However, the body of the request is null inside my Lambda function.
I looked at this question: AWS Lambda Go function not getting request body when called via API GW
I am trying to replicate the answer there by using this library: https://github.com/aws/aws-lambda-go/blob/master/events/apigw.go, but I'm still not able to get the request body.
Here is my Lambda code:
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
fmt.Println("Body")
fmt.Println(request.Body)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Body size = %d.\n", len(request.Body))
fmt.Println("Headers:")
for key, value := range request.Headers {
fmt.Printf(" %s: %s\n", key, value)
}
return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
}
func main() {
lambda.Start(handleRequest)
}
I'm expecting to see some data after "Body" in Cloudwatch, but there is nothing.
The code in the original question is correct. The second argument to the handleRequest is of the type APIGatewayProxyRequest. In API Gateway, I was sending a normal request, not a proxy request. I redeployed my API Gateway route as a proxy request and got the request body I was expecting. I'm still not really sure whether my original request was failing to send the body, or if the structure of a normal request passed into the handleRequest function is different from that of a proxy request and so the APIGatewayProxyRequest type was unable to parse its body.

How do I redirect a GET request to a POST request with some data?

When a user hits a certain url with a GET request I'd like to redirect them to a POST request at another location.
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
)
func old(w http.ResponseWriter, r *http.Request) {
newURL := "/new"
var bdy = []byte(`title=Buy cheese and bread for breakfast.`)
r.Method = "POST"
r.URL, _ = url.Parse(newURL)
r.RequestURI = newURL
r.Body = ioutil.NopCloser(bytes.NewReader(bdy))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
http.Redirect(w, r, newURL, 302)
}
func new(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Printf("Method:%v\n", r.Method)
fmt.Printf("Title:%v\n", r.Form.Get("title"))
}
func main() {
http.HandleFunc("/", old)
http.HandleFunc("/new", new)
port := 8000
fmt.Printf("listening on %v\n", port)
if err := http.ListenAndServe(fmt.Sprintf(":%v", port), nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
When I hit "/" I end up getting redirected to "/new" but with a GET request and no form data:
Method:GET
Title:
If I curl "/new" directly I get :
curl -XPOST localhost:8000/new -d "title=Buy cheese and bread for breakfast."
Method:POST
Title:Buy cheese and bread for breakfast.
A HTTP redirect (i.e. reply with status code 301, 302, 307,308 and Location header) can only redirect the existing request to another location and not change the payload of the request. It can add some cookies in the response header though.
In order to automatically change a GET request into a POST request with a specific payload you might try to send the client a HTML page with a <form method=POST... and the payload with hidden input fields, i.e. <input name=... value=... type=hidden> and then add some JavaScript to the page which automatically submits the form. But this kind of hack will only work in browsers and only if JavaScript is enabled and will not work with all kind of payloads either.
To keep compatibility with a broader range of clients it is probably better to design it differently, i.e. keep the GET request in the redirect but give the necessary payload as a parameter to the new target, i.e. http://new.target/foo?payload=..... But the details depend on what the target of the request can deal with.
Unfortunately I don't believe a redirect can change the verb (e.g., GET, POST) or add data to the request. It can only change the URL.
See Redirect () for more information.
I've never heard about changing verb from GET to POST. I guess it's impossible because POST supposes body of body (however may be empty) and GET doesn't. So in general case browser would not be able to take the body from nothing.
Otherwise is possible: you may send 302 redirect after post to make browser perform get. Also verb can be kept with 307 reply code.
Try to rethink browser-server interaction. May be you can redirect POST to another location to solve a task?

Resources