I'm studying Golang and have question on context.
I want to send request from server1 to server2 with context and want to read context value set by server1.
But it seems context of sercer2 doesn't carry server1's context value.
When I send request to server1 by curl http://localhost:8080
server1's console.
Send request to http://localhost:8082
server2's console.
request coming in
<nil>
How can I retrieve context value set by server1?
And if it is possible, I also want to know whether it is a correct way of exchanging value's like authentication between servers.
Middleware pattern is more desirable?
Thank you.
Codes
package main
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.HandleFunc("/", hello)
fmt.Println("Starting listening on port 8080...")
http.ListenAndServe(":8080", r)
}
// Context's key.
type Sample string
var sampleKey Sample = "sample"
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Println("Send request to http://localhost:8082")
ctx := context.WithValue(context.Background(), sampleKey, "1234")
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8082", nil)
if err != nil {
fmt.Println("Error while sending request: ", err)
}
// Send request.
var c http.Client
c.Do(req)
}
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.HandleFunc("/", receive)
fmt.Println("Start listening on port 8082...")
http.ListenAndServe(":8082", r)
}
type Sample string
var sampleKey Sample
func receive(w http.ResponseWriter, r *http.Request) {
fmt.Println("request coming in")
fmt.Println(r.Context().Value(sampleKey))
}
The context is supposed to be used in a function call stack to share information and cancellation point between these functions. There is no automatic mechanism to serialize the value bound in the context over http. If you want to send data over http between these two servers, you mostly have three solution, you can:
encode that as an url parameter in the path somewhere
GET /hello/1234
send that in the http body, for example in JSON
POST /hello
{
"sample": 1234
}
as url key/value encoded as url parameters
/hello?sample=1234
I have reverse proxies in my main web-server that are dedicated to a certain micro-service and handle forward requests to their appropriate micro-services.
func newTrimPrefixReverseProxy(target *url.URL, prefix string) *httputil.ReverseProxy {
director := func(req *http.Request) {
// ... trims prefix from request path and prepends the path of the target url
}
return &httputil.ReverseProxy{Director: director}
}
This has worked perfectly for pure JSON responses, but I have ran into issues recently when trying to serve content (stream responses) through the reverse proxy. The means for serving the content is irrelevant, the (video) content is served as intended when the service is accessed directly and not through the reverse proxy.
Serving the content:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.ServeContent(w, r, "video.mp4", time.Now().Add(time.Hour*24*365*12*9*-1), videoReadSeeker)
})
Again, the videoReadSeeker and how the content is served is not the issue, the issue is having my response relayed as intended to the requester through the reverse proxy; when accessing the service directly, the video shows up and I can scrub it to my heart's content.
Note that the response for data the content is received (http status, headers), but the content stream in the response body is not.
How can I make sure that the reverse proxy handles streamed responses as intended for the content?
Do you get the same results when using:
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
func main() {
u, err := url.Parse("http://localhost:8080/asdfasdf")
if err != nil {
log.Fatal("url.Parse: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(u)
log.Printf("Listening at :8081")
if err := http.ListenAndServe(":8081", proxy); err != nil {
log.Fatal("ListenAndServe: %v", err)
}
}
Ultimately these are the same implementation under the hood, but the director provided here ensures that some of the expected headers exist that you will need for some proxy features to function as expected.
I have no idea why I always receive an empty string when sending values in POSTMAN
func main(){
rtr := mux.NewRouter()
rtr.HandleFunc("/search", search).Methods("POST")
}
func search(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name") //returns empty
}
This is the body request in POSTMAN
screenshot for the body request
{
"name": "markus"
}
I tried to change the body request to form data
Screenshot for form data in post request
But it still didn't work.
Does anyone have a solution?
Thanks
What you have there is not a FormValue but a JSON body. If your JSON object is just a simple map of string to string, then you can do something like this:
func search(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body) // check for errors
keyVal := make(map[string]string)
json.Unmarshal(body, &keyVal) // check for errors
name := keyVal["name"]
// do whatever with name
}
Edit
If you need to parse a form value you need to call ParseForm()
func search(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
// handle err
}
name := r.FormValue("name")
}
Just want to share additional information here.
Please check the Content-Type in Header section of Postman if you are facing any problem in sending your request to the server.
Set Content-Type to application/json for sending raw JSON in request.
Set Content-Type to application/x-www-form-urlencoded if you are sending form values in request. Also select x-www-form-urlencoded in postman's Body section
I have the following code:
package main
import (
"bytes"
"fmt"
"net/http"
)
func main() {
var jsonStr = []byte(`{"title":"Buy cheese and bread for breakfast."}`)
req, err := http.NewRequest("POST", "/", bytes.NewBuffer(jsonStr))
if err != nil {
panic(err)
}
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
req.ParseForm()
fmt.Printf("%v:%v", "title", req.Form.Get("title"))
}
I am unable to extract the "title" param and not sure why.
As noted in the GoDoc for the http.Request.ParseForm method, the type of the body must be application/x-www-form-urlencoded, not JSON like your current example:
For other HTTP methods, or when the Content-Type is not application/x-www-form-urlencoded, the request Body is not read, and r.PostForm is initialized to a non-nil, empty value.
Here is an updated example of your code using a form body, which gives the intended result: https://play.golang.org/p/Zrw05T2Zb5Z
If you want to extract values from a JSON body, that can be done using a method such as json.Unmarshal, however a JSON body doesn't represent a form.
The 3rd argument of http.NewRequest is the http payload.
In your case, payload type is application/json. It's need to be treated as json, only then you'll be able to get certain value from the it. In this case, we just cannot use the same technique like on getting value from query string or form data.
So just unmarshal the jsonStr data into map or struct.
res := make(map[string]interface{})
err := json.Unmarshal(jsonStr, &res)
if err != nil {
panic(err)
}
fmt.Printf("%#v \n", res["title"])
To be honest I'm quite confused with your question, why you need to get the payload from http client request.
If what you want is actually how to get the payload from the web server end, you can get it by decoding the request body. Example:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
payload := make(map[string]interface{})
err := json.NewDecoder(r.Body).Decode(&payload)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
title := payload["title"].(string)
w.Write([]byte(title))
})
Curl example (based on your code):
curl -d '{"title":"Buy cheese and bread for breakfast."}' \
-H "Content-Type: application/json" \
-X POST http://localhost:9000
Output:
Buy cheese and bread for breakfast.
Because your request isn't a form. It doesn't have any GET parameters, and it isn't form-encoded data.
For other HTTP methods, or when the Content-Type is not application/x-www-form-urlencoded, the request Body is not read, and r.PostForm is initialized to a non-nil, empty value.
[ https://golang.org/pkg/net/http/#Request.ParseForm ]
You're free to parse the body of the request as application/json, but that isn't the same as form data.
I'm trying to set an http header for multiple handlers. My first thought was to make a custom write function that would set the header before writing the response like the code sample at the bottom.
However, when I pass a pointer to the http.ResponseWriter and try to access it from my function it tells me that "type *http.ResponseWriter has no Header method".
What is the best way to set headers for multiple handlers, and also why isn't the pointer working the way I want it to?
func HelloServer(w http.ResponseWriter, req *http.Request) {
type Message struct {
Name string
Body string
Time int64
}
m := Message{"Alice", "Hello", 1294706395881547000}
b, _ := json.Marshal(m)
WriteJSON(&w, b)
}
func WriteJSON(wr *http.ResponseWriter, rawJSON []byte) {
*wr.Header().Set("Content-Type", "application/json")
io.WriteString(*wr, string(rawJSON))
}
func main() {
http.HandleFunc("/json", HelloServer)
err := http.ListenAndServe(":9000", nil)
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}
I'm not sure about the multiple handlers thing, but I do know why the code you wrote is failing. The key is that the line:
*wr.Header().Set("Content-Type", "application/json")
is being interpreted, because of operator precedence, as:
*(wr.Header().Set("Content-Type", "application/json"))
Since wr has the type *http.ResponseWriter, which is a pointer to and interface, rather than the interface itself, this won't work. I assume that you knew that, which is why you did *wr. I assume what you meant to imply to the compiler is:
(*wr).Header().Set("Content-Type", "application/json")
If I'm not mistaken, that should compile and behave properly.
You don't need to use *wr as it already references a pointer.
wr.Header().Set("Content-Type", "application/json") should be sufficient.
If you want to set "global" headers for every request, you can create a function that satisfies http.HandleFunc (go.auth has a good example) and then wrap your handlers like so:
http.HandleFunc("/hello", Defaults(helloHandler))
Also take a look at the net/http documentation, which has further examples.
I wrap my handlers with an error handler
which calls my AddSafeHeader function.
I based it on http://golang.org/doc/articles/error_handling.html
but it doesn't use ServeHTTP so it works with appstats:
http.Handle("/", appstats.NewHandler(util.ErrorHandler(rootHandler)))
Here:
package httputil
import (
"appengine"
"net/http"
"html/template"
)
func AddSafeHeaders(w http.ResponseWriter) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
w.Header().Set("Strict-Transport-Security", "max-age=2592000; includeSubDomains")
}
// Redirect to a fixed URL
type redirectHandler struct {
url string
code int
}
func (rh *redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Redirect(w, r, rh.url, rh.code)
}
func Redirect(w http.ResponseWriter, r *http.Request, urlStr string, code int) {
AddSafeHeaders(w)
http.Redirect(w, r, urlStr, code)
}
// RedirectHandler returns a request handler that redirects
// each request it receives to the given url using the given
// status code.
func RedirectHandler(url string, code int) http.Handler {
return &redirectHandler{url, code}
}
func ErrorHandler(fn func(appengine.Context, http.ResponseWriter, *http.Request)) func(appengine.Context, http.ResponseWriter, *http.Request) {
return func(c appengine.Context, w http.ResponseWriter, r *http.Request) {
defer func() {
if err, ok := recover().(error); ok {
c.Errorf("%v", err)
w.WriteHeader(http.StatusInternalServerError)
errorTemplate.Execute(w, err)
}
}()
AddSafeHeaders(w)
fn(c, w, r)
}
}
// Check aborts the current execution if err is non-nil.
func Check(err error) {
if err != nil {
panic(err)
}
}
var errorTemplate = template.Must(template.New("error").Parse(errorTemplateHTML))
const errorTemplateHTML = `
<html>
<head>
<title>XXX</title>
</head>
<body>
<h2>An error occurred:</h2>
<p>{{.}}</p>
</body>
</html>
`
http.ResponseWriter is an interface.
You should probably not be using a pointer to an interface. In net/http/server.go, the unexported response struct is the actual type that implements ResponseWriter when the server calls your handler, and importantly, when the server actually calls the handler's ServeHTTP, it passes a *response. It's already a pointer, but you don't see that because ResonseWriter is an interface. (the response pointer is created here, by (c *conn).readRequest. (The links will likely be for the wrong lines the future, but you should be able to locate them).
That's why the ServeHTTP function required to implement Handler is:
ServeHTTP(w ResponseWriter, r *Request)
i.e. not a pointer to ResponseWriter, as this declaration already permits a pointer to a struct that implements the ResponseWriter interface.
As I am new to Go, I created a minimal contrived example, based on elithrar's answer, which shows how to easily add headers to all your routes / responses. We do so, by creating a function that satisfies the http.HandlerFunc interface, then wraps the route handler functions:
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
// Hello world.
func Hello(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode("Hello World")
}
// HelloTwo world
func HelloTwo(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode("Hello Two World")
}
// JSONHeaders conforms to the http.HandlerFunc interface, and
// adds the Content-Type: application/json header to each response.
func JSONHeaders(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
handler(w, r)
}
}
func main() {
router := mux.NewRouter()
// Now, instead of calling your handler function directly, pass it into the wrapper function.
router.HandleFunc("/", JSONHeaders(Hello)).Methods("GET")
router.HandleFunc("/hellotwo", JSONHeaders(HelloTwo)).Methods("GET")
log.Fatal(http.ListenAndServe(":3000", router))
}
Results:
$ go run test.go &
$ curl -i localhost:3000/
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 28 Feb 2019 22:27:04 GMT
Content-Length: 14
"Hello World"
What i end up doing:
// Accepts a user supplied http.HandlerFunc and then modifies it in various ways. In this case, it adds two new headers.
func CommonlHandler(h http.HandlerFunc) http.HandlerFunc {
return func (rs http.ResponseWriter, rq *http.Request) {
rs.Header().Add("Server", "Some server")
rs.Header().Add("Cache-Control", "no-store")
h(rs, rq)
}
// somewhere down the line, where you're setting up http request handlers
serveMux := http.NewServeMux()
serveMux.HandleFunc("/", CommonHandler(func(rs http.ResponseWriter, rq *http.Request) {
// Handle request as usual. Since it's wrapped in the CommonHandler and we've set some headers there, responses to requests to "/" will contain those headers.
// Be mindful what modifications you're doing here. If, for ex., you decide you want to apply different caching strategy than the Common one, since this will be passed to the CommonHandler, your changes will be overwritten and lost. So it may be a good idea to introduce checks in CommonHandler to determine whether the header exists, before you decide to create it.
}))
serveMux.HandleFunc("/contact", CommonHandler(func(rs http.ResponseWriter, rq *http.Request) {
// handle another request as usual
}))