Problem: You want to make an HTTP request to a web server.
Solution: Use the net/http package to make an HTTP request.
HTTP is a request-respond protocol, and serving requests is only half of the story. The other half is making requests. The net/http package provides functions to make HTTP requests. You will start with the two most common HTTP request methods, GET and POST , which have their own convenience functions.
The http.Get function is the most basic HTTP client function in the net/http package. It simply makes a GET request to the specified URL and returns an http.Response and an error. The http.Response has a Body field that you can read to get the response body.
Here’s a simple example:
func main() { resp, err := http.Get("https://www.ietf.org/rfc/rfc2616.txt") if err != nil { // resolve error } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { // resolve error } fmt.Println(string(body)) }
The body is a slice of bytes, which you can convert into a string. If you run it on a terminal, you’ll get the entire RFC 2616 document in text.
Next, you can make a POST request. Start with making a POST request that sends a JSON message to a server. For this example, you’ll use the http://httpbin.org/post endpoint, which will echo back the request body. HTTPBin is an open source tool that provides a set of endpoints you can use to test your HTTP clients.
This example and the following examples will not handle errors for brevity, but you should always handle errors properly in your code:
func main() { msg := strings.NewReader(`{"message": "Hello, World!"}`) resp, _ := http.Post("https://httpbin.org/post", "application/json", msg) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
The http.Post function takes the URL, the content type, and a request body that is of type io.Reader as parameters. You used strings.NewReader to create a string.Reader (which is an io.Reader , of course) from the JSON message, and then pass it to http.Post as the request body.
As before, the http.Post function returns an http.Response and an error, and you can read the response body and print it out.
When you run it from the terminal, you get the following output:
zzh@ZZHPC:/zdata/MyPrograms/Go/testing$ go run main.go { "args": {}, "data": "{\"message\": \"Hello, World!\"}", "files": {}, "form": {}, "headers": { "Accept-Encoding": "gzip", "Content-Length": "30", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=1-652e94d5-0dda97772c075d4b62545b1a" }, "json": { "message": "Hello, World!" }, "origin": "111.32.65.10", "url": "https://httpbin.org/post" }
A very common use case is to send data from an HTML form to a server with the POST method. The http.PostForm function makes it easy to send form data to a server. It takes the URL and a url.Values as parameters.
The url.Values type is a map of string keys and string values found in the net/url package. The url.Values type has an Add method that you can use to add values to the map. Each key can have multiple values. The http.PostForm function will encode the form data and send it to the server:
func main() { form := url.Values{} form.Add("message", "Hello, World!") resp, _ := http.PostForm("https://httpbin.org/post", form) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
When you run it from the terminal, you get the following output:
zzh@ZZHPC:/zdata/MyPrograms/Go/testing$ go run main.go { "args": {}, "data": "", "files": {}, "form": { "message": "Hello, World!" }, "headers": { "Accept-Encoding": "gzip", "Content-Length": "26", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=1-652e95d6-669cd89c24f5cefe436cade6" }, "json": null, "origin": "111.32.65.10", "url": "https://httpbin.org/post" }
The net/http package also provides a more generic function to make HTTP requests using http.Client , which is quite straightforward. You create an instance of the http.Client , and then you call the Do method, passing in an http.Request as the parameter. This will return an http.Response and an error as before.
To make it even simpler, the net/http package even provides an http.DefaultClient instance that you can use immediately, with default settings.
Here’s how this can be done. This time around, you’ll add a cookie to the request:
func main() { req, _ := http.NewRequest("GET", "https://httpbin.org/cookies", nil) req.AddCookie(&http.Cookie{ Name: "foo", Value: "bar", }) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
First, create an http.Request using the http.NewRequest function. Then pass in the HTTP method, the URL, and an io.Reader as the request body. The http.NewRequest function returns an http.Request and an error (which you disregard for brevity as in the previous code). Then add a cookie to the request using the http.Request.AddCookie method.
Finally, call the http.DefaultClient.Do method, passing in the http.Request as the parameter. This will return an http.Response and an error.
When you run it from the terminal, you get the following output:
zzh@ZZHPC:/zdata/MyPrograms/Go/testing$ go run main.go
{
"cookies": {
"foo": "bar"
}
}
You can do the same with POST requests as well:
func main() { msg := strings.NewReader(`{"message": "Hello, World!"}`) req, _ := http.NewRequest("POST", "https://httpbin.org/post", msg) req.Header.Add("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
The output is the same as using the http.Post function:
zzh@ZZHPC:/zdata/MyPrograms/Go/testing$ go run main.go { "args": {}, "data": "{\"message\": \"Hello, World!\"}", "files": {}, "form": {}, "headers": { "Accept-Encoding": "gzip", "Content-Length": "30", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=1-652e9808-573b82c37669626a430d0013" }, "json": { "message": "Hello, World!" }, "origin": "111.32.65.10", "url": "https://httpbin.org/post" }
So far, you’ve only been sending GET and POST requests. The net/http package also supports other HTTP methods like PUT , PATCH , DELETE , and so on, using the http.Client mechanism.
You can give PUT a try:
func main() { msg := strings.NewReader(`{"message": "Hello, World!"}`) req, _ := http.NewRequest("PUT", "https://httpbin.org/put", msg) req.Header.Add("Content-Type", "application/json") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) }
If you look at the output at the terminal, it’s almost the same as with POST :
zzh@ZZHPC:/zdata/MyPrograms/Go/testing$ go run main.go { "args": {}, "data": "{\"message\": \"Hello, World!\"}", "files": {}, "form": {}, "headers": { "Accept-Encoding": "gzip", "Content-Length": "30", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Go-http-client/2.0", "X-Amzn-Trace-Id": "Root=1-652e98e0-382f1b5441741b513cc3c830" }, "json": { "message": "Hello, World!" }, "origin": "111.32.65.10", "url": "https://httpbin.org/put" }