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

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?

Related

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.

307 redirect with Authorization header

In looking at the Go docs for http it looks like the Authorization header is removed when a response is a 307. Obviously it makes sense for almost every case but is there a way not to remove the Authorization header?
You can modify your http.Client to add the header again after it has been removed using CheckRedirect:
CheckRedirect func(req *Request, via []*Request) error
Since req is the upcoming request, it can be modified before it is sent. After making the changes, return nil to indicate that the request should still be sent.
Since this is a change to the http client instead of the request, you should check that this redirect is only used for the one URL where you need it (in case you use that client to do other requests).
You client definition could look like this:
http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// you can check old responses for a status code
if len(via) != 0 && via[0].Response.StatusCode == http.StatusTemporaryRedirect {
req.Header.Add("Authorization", "some-value")
}
return nil
},
}

Go http response few headers

I'm want send to user alert if he type wrong password and return it to page were he type password. I'm making it like this
func sendJSONHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
http.ServeFile(w, r, "template/api/api.html")
} else if r.Method == "POST" {
r.ParseForm()
if r.Form["password"][0] == "apiPassword" {
j := struct {
Proxies []string
}{Proxies: code.UP.Proxy}
w.Header().Set("Access-Control-Allow-Origin", corsAddrSite)
json.NewEncoder(w).Encode(j)
} else {
// here is a problem
fmt.Fprintln(w, "<script>alert('Wrong Password')</script>")
http.ServeFile(w, r, "template/api/api.html")
}
}
}
But i'v get http: multiple response.WriteHeader calls error.
How to do it right?
You cannot write to the http.ResponseWriter more than once depending on the HTTP spec.
from the go docs https://golang.org/pkg/net/http/#ResponseWriter
To solve your issue, you could have the script tags inside the template file, or make a new template. You could also tailor the response by adding the alert script before you send it. Maybe with template files.
However a proper solution to this problem might be to have more logic in the actual html served, the front end should display a response based on the status code or response body.

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

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

How to allow OPTIONS method from mobile using gorilla handler?

Need to accept OPTIONS method coming from mobile device,
attempted multiple ways to do so and getting strange behavior:
when trying this I get 403 from the client:
(client sends OPTIONS before POST)
import (
"net/http"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/users", UserEndpoint)
r.HandleFunc("/projects", ProjectEndpoint)
methods := handlers.AllowedMethods([]string{"OPTIONS", "DELETE", "GET", "HEAD", "POST"}
http.ListenAndServe(":8000", handlers.CORS(methods)(r))
}
if I omit the methods:
http.ListenAndServe(":8000", handlers.CORS()(r))
I get 403 not authorized
Also played around with it, removed the GET method:
methods := handlers.AllowedMethods([]string{"OPTIONS"}
http.ListenAndServe(":8000", handlers.CORS(methods)(r))
but still could
get a 200 GET when tried from rest client in browser (chromes DHC)
but if I remove the OPTIONS:
methods := handlers.AllowedMethods([]string{"DELETE", "GET", "HEAD", "POST"}
http.ListenAndServe(":8000", handlers.CORS(methods)(r))
I get 405
First example is based on gorilla handler docs
Any ideas on this issues?
Thanks
You really need to understand the request being made, but I had a similar problem and resolved it with:
handlers.CORS(
handlers.AllowedOrigins([]string{"*"}),
handlers.AllowedMethods([]string{"POST"}),
handlers.AllowedHeaders([]string{"Content-Type", "X-Requested-With"}),
)(router)
The request I needed to make (which mimics a preflight) was:
curl -H "Origin: http://example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: X-Requested-With" \
-X OPTIONS --verbose http://127.0.0.1:8080/products
It was really the AllowedHeaders func that made all the difference. As soon as I added that, the 403 error disappeared.
If you look at cors.go Options are specially handled:
corsOptionMethod string = "OPTIONS"
...
if r.Method == corsOptionMethod {
if ch.ignoreOptions {
ch.h.ServeHTTP(w, r)
return
}
if _, ok := r.Header[corsRequestMethodHeader]; !ok {
w.WriteHeader(http.StatusBadRequest)
return
}
method := r.Header.Get(corsRequestMethodHeader)
if !ch.isMatch(method, ch.allowedMethods) {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
...
So 405 is http.StatusMethodNotAllowed, so maybe it is not CORs request header?
There is also an IngoreOptions method for handling Options independely: http://www.gorillatoolkit.org/pkg/handlers#IgnoreOptions - maybe that will work in your case and you can just ignore it, or process Options on your own.

Resources