sending cookie in API Rest golang - rest

am working in Golang, I am building an API-Rest and am wondering, can I set cookies using restful?
I am building the methos related to the authentication of the users: login, logout,sign up, etc. and by now am trying to set a cookie in the response with the generated uuid. I have this:
func Login(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
...some code....
c := &http.Cookie{
Name: "session",
Value: uuid.NewV4().String(),
}
http.SetCookie(w, c)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
json.NewEncoder(w).Encode(user)
w.WriteHeader(fasthttp.StatusOK)
}
But in the response I don't get any cookie, so, if is possible, how is the proper way to make it? Thank you!

You can indeed set cookies.
This would feel like it's too short of an answer though. Remember that a REST API is nothing more than a HTTP server with a very strict usage of how it should be called and what it returns. As such, you can safely set cookies.
The question is though, if that is really something you should do, have a look at JSON Web Tokens and JSON Web Encryption instead. There are Go libraries available for both. The rationale for using JWE and JWT over cookies is that you usually want a REST API to be as stateless as possible; preferring for the Client to keep state instead.
If you insist on using cookies though, consider using Gorilla's securecookie API instead, as you probably do not want people peeking into your cookie's contents. You can use it as so:
import "github.com/gorilla/securecookie"
s := securecoookie.New([]byte("very-secret-1234"), byte[]("much-hidden-5678"))
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := s.Encode("cookie-name", value); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
Secure: true,
HttpOnly: true,
}
http.SetCookie(w, cookie)
}
}
Similarly, you can retrieve the Cookie's contents like this:
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}

Related

Request created with http.NewRequestWithContext() looses context when passed to middleware

In program bellow I have two routers. One is working at localhost:3000 and acts like a public access point. It also may send requests with data to another local address which is localhost:8000 where data is being processed. Second router is working at localhost:8000 and handles processing requests for the first router.
Problem
The first router sends a request with context to the second using http.NewRequestWithContext() function. The value is being added to the context and the context is added to request. When request arrives to the second router it does not have value that was added previously.
Some things like error handling are not being written to not post a wall of code here.
package main
import (
"bytes"
"context"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
func main() {
go func() {
err := http.ListenAndServe(
"localhost:3000",
GetDataAndSolve(),
)
if err != nil {
panic(err)
}
}()
go func() {
err := http.ListenAndServe( // in GetDataAndSolve() we send requests
"localhost:8000", // with data for processing
InternalService(),
)
if err != nil {
panic(err)
}
}()
// interrupt := make(chan os.Signal, 1)
// signal.Notify(interrupt, syscall.SIGTERM, syscall.SIGINT)
// <-interrupt // just a cool way to close the program, uncomment if you need it
}
func GetDataAndSolve() http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/tasks/str", func(rw http.ResponseWriter, r *http.Request) {
// receiving data for processing...
taskCtx := context.WithValue(r.Context(), "str", "strVar") // the value is being
postReq, err := http.NewRequestWithContext( // stored to context
taskCtx, // context is being given to request
"POST",
"http://localhost:8000/tasks/solution",
bytes.NewBuffer([]byte("something")),
)
postReq.Header.Set("Content-Type", "application/json") // specifying for endpoint
if err != nil { // what we are sending
return
}
resp, err := http.DefaultClient.Do(postReq) // running actual request
// pls, proceed to Solver()
// do stuff to resp
// also despite arriving to middleware without right context
// here resp contains a request with correct context
})
return r
}
func Solver(next http.Handler) http.Handler { // here we end up after sending postReq
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
if r.Context().Value("str").(string) == "" {
return // the request arrive without "str" in its context
}
ctxWithResult := context.WithValue(r.Context(), "result", mockFunc(r.Context()))
next.ServeHTTP(rw, r.Clone(ctxWithResult))
})
}
func InternalService() http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.With(Solver).Post("/tasks/solution", emptyHandlerFunc)
return r
}
Your understanding of context is not correct.
Context (simplifying to an extent and in reference to NewRequestWithContext API), is just an in-memory object using which you can control the lifetime of the request (Handling/Triggering cancellations).
However your code is making a HTTP call, which goes over the wire (marshaled) using HTTP protocol. This protocol doesn't understand golang's context or its values.
In your scenario, both /tasks/str and /tasks/solution are being run on the same server. What if they were on different servers, probably different languages and application servers as well, So the context cannot be sent across.
Since the APIs are within the same server, maybe you can avoid making a full blown HTTP call and resort to directly invoking the API/Method. It might turn out to be faster as well.
If you still want to send additional values from context, then you'll have to make use of other attributes like HTTP Headers, Params, Body to send across the required information. This can provide more info on how to serialize data from context over HTTP.

Sending data from client to server - REST webservice

I am developing a client-server application using RESTful web services.
I want to ask for user input on the client and send it to the server and use that name in the rest of my program but I cannot send the name to the server properly.
Below is a part of my program:
Client:
func main() {
//getting input
fmt.Println("Please enter your name: ")
reader := bufio.NewReader(os.Stdin)
myName, _ := reader.ReadString('\n')
client := &http.Client{
CheckRedirect: nil,
}
reply, err := http.NewRequest("GET", "http://localhost:8080/", nil)
reply.Header.Add("username", myName)
client.Do(reply)
if err != nil {
fmt.Println(err)
}
Server:
func CreateClient(w http.ResponseWriter, r *http.Request) {
clientName := r.Header.Get("username")
fmt.Println(clientName, "---------")//it's empty
cli := &Client{
currentRoom: nil, //starts as nil because the user is not initally in a room
outputChannel: make(chan string),
name: clientName,
}
Members = append(Members, cli)
reply := cli.name
fmt.Fprintf(w, reply)
}
on the client side, reply (reply.Header.Add("username", myName)) has the user name in the header but on the server side clientName (clientName := r.Header.Get("username")) is empty so the rest of my program won't run.
My problem is that I cannot send the user input to the server and take it back on the client side.
Can someone tell me how I can solve the problem?
You may want to have a look at http query string. Wikipedia
Sending the username.
req := http.NewRequest("GET","localhost:8080",nil)
q:=req.URL.Query()
q.Add("username",myName)
req.URL.RawQuery = q.Encode()
Resp,err:=http. Client{}.Do(req)
Recieving the username:
clientName := r.URL.Query()["username"][0]
Note that if your are going to do the same with password or any other sensitive data, please do some research on how to make it secure and DO NOT copy this code.

Golang HTTP REST mocking

I writing a client that connects to a server with REST endpoints. The client needs to make a chain of 11 different requests to complete an action (it's a rococo backup system).
I'm writing my client in Go and I also want to write my mocks/tests in Go. What I'm unclear about is how a test called func TestMain would call into the client's func main(), to test completion of the chain of 11 requests.
My client's binary would be run from the shell in the following way:
$ client_id=12345 region=apac3 backup
How would I call func main() from the tests, with environment variables set? Or is there another approach? (I'm comfortable writing tests, so that's not the issue)
I'm looking at the Advanced Example in jarcoal/httpmock (but I could use another library). At the end the example says // do stuff that adds and checks articles, is that where I would call main()?
I've pasted the Advanced Example below, for future reference.
func TestFetchArticles(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
// our database of articles
articles := make([]map[string]interface{}, 0)
// mock to list out the articles
httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
func(req *http.Request) (*http.Response, error) {
resp, err := httpmock.NewJsonResponse(200, articles)
if err != nil {
return httpmock.NewStringResponse(500, ""), nil
}
return resp, nil
},
)
// mock to add a new article
httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles.json",
func(req *http.Request) (*http.Response, error) {
article := make(map[string]interface{})
if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
return httpmock.NewStringResponse(400, ""), nil
}
articles = append(articles, article)
resp, err := httpmock.NewJsonResponse(200, article)
if err != nil {
return httpmock.NewStringResponse(500, ""), nil
}
return resp, nil
},
)
// do stuff that adds and checks articles
}
Writing this out helped me answer my own question.
main() would read in environment variables and then call a function like doBackup(client_id, region). My test would mock the endpoints and then call doBackup(client_id, region).

Go web service - POST tar.gz file as request body

I need to implement web service in go that processes tar.gz files and I wonder what is the correct way, what content type I need to define, etc.
plus, I found that a lot of things are handled automatically - on the client side I just post a gzip reader as request body and Accept-Encoding: gzip header is added automatically, and on the server side - I do not need to gunzip the request body, it is already extracted to tar. does that make sense?
Can I rely that it would be like this with any client?
Server:
func main() {
router := mux.NewRouter().StrictSlash(true)
router.Handle("/results", dataupload.NewUploadHandler()).Methods("POST")
log.Fatal(http.ListenAndServe(*address, router))
}
Uploader:
package dataupload
import (
"errors"
log "github.com/Sirupsen/logrus"
"io"
"net/http"
)
// UploadHandler responds to /results http request, which is the result-service rest API for uploading results
type UploadHandler struct {
uploader Uploader
}
// NewUploadHandler creates UploadHandler instance
func NewUploadHandler() *UploadHandler {
return &UploadHandler{
uploader: TarUploader{},
}
}
func (uh UploadHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
retStatus := http.StatusOK
body, err := getBody(request)
if err != nil {
retStatus = http.StatusBadRequest
log.Error("Error fetching request body. ", err)
} else {
_, err := uh.uploader.Upload(body)
}
writer.WriteHeader(retStatus)
}
func getBody(request *http.Request) (io.ReadCloser, error) {
requestBody := request.Body
if requestBody == nil {
return nil, errors.New("Empty request body")
}
var err error
// this part is commented out since somehow the body is already gunzipped - no need to extract it.
/*if strings.Contains(request.Header.Get("Accept-Encoding"), "gzip") {
requestBody, err = gzip.NewReader(requestBody)
}*/
return requestBody, err
}
Client
func main() {
f, err := os.Open("test.tar.gz")
if err != nil {
log.Fatalf("error openning file %s", err)
}
defer f.Close()
client := new(http.Client)
reader, err := gzip.NewReader(f)
if err != nil {
log.Fatalf("error gzip file %s", err)
}
request, err := http.NewRequest("POST", "http://localhost:8080/results", reader)
_, err = client.Do(request)
if err != nil {
log.Fatalf("error uploading file %s", err)
}
}
The code you've written for the client is just sending the tarfile directly because of this code:
reader, err := gzip.NewReader(f)
...
request, err := http.NewRequest("POST", "http://localhost:8080/results", reader)
If you sent the .tar.gz file content directly, then you would need to gunzip it on the server. E.g.:
request, err := http.NewRequest(..., f)
I think that's closer to the behavior you should expect third-party clients to exhibit.
Claerly not, but maybe...
Golang provides a very good support for the http client (and server). This is one of the first language to support http2 and the design of the API clearly shows their concern on having a fast http.
This is why they add Accept-Econding: gzip automatically. That will dramatically reduce the size of the server response and then optimize the transfer.
But the gzip remains an option in http 1 and not all of the client will push this header to your server.
Note that the Content-Type describes the type of data you are sending (here a tar.gz but could be application/json, test/javascript, ...), when the Accept-Encoding describes the way the data has been encoded for the transport
Go will take care of transparently handling the Accept-Encoding for you because it is responsible of the transport of the data. Then it will be up to you to handle the Content-Type because only you know how to give a sense to the content you received

Submitting form with golang http library

Oke, I'm currently trying to login in to my school website, with my own Crawler. Altough they have some protection against login. First I do a Get request to the Website so I get the token from the hidden Input field. That token I use in my next Post request to login to the url! But for some reason the http response is that I cannot resubmit the form. But with doing the same in Postman rest client (chrome plugin) I can login!
When I try to submit a form to this url:
postLoginUrl = "?username=%s&password=%s&submit=inloggen&_eventId=submit&credentialsType=ldap&lt=%s"
loginUrl = "https://login.hro.nl/v1/login"
where %s are filled in credentials
req, err := client.Post(loginUrl, "application/x-www-form-urlencoded", strings.NewReader(uri))
I'm getting as response that the Form cannot be resubmitted.
But when I try it with Postman rest client, I'm allowed to login.
code for Csrf token:
func getCSRFtoken() (key string) {
doc, err := goquery.NewDocument(loginUrl)
if err != nil {
log.Fatal(err)
}
types := doc.Find("input")
for node := range types.Nodes {
singlething := types.Eq(node)
hidden_input, _ := singlething.Attr("type")
if hidden_input == "hidden" {
key, _ := singlething.Attr("value")
return key
}
}
return ""
}
goquery.NewDocument is a http.Get()
My question now is, how does the URL get's formatted from the library
Maybe you would be better off using:
(c *Client)PostForm(url string, data url.Values) (resp *Response, err error)
from net/http like http://play.golang.org/p/8D6XI6arkz
With the params in url.Values (instead of concatenating the strings, like you are doing now.)