I am developing an application in Go which uses the net/http package to make a number of external http requests/calls which I would like to monitor the http calls' latency, response times, status code etc.
As there are a number of endpoints which my applications calls, and there could be more that will be added in the future, I would like to consider a solution which will obtain the metrics for all such external http calls.
Any suggestions, guidance or examples on this is much appreciated.
I am thinking of some way in which a middleware/wrapper is added onto the Do/Raw call within the net/http package which I can create and modify a copy of but I'm not exactly sure how to do that.
Here's a very (!) basic example that abstracts http.Client's Do method with a Prometheus Counter that Inc's on every Do and labels the Counter with the host's name, the request method and the response code:
package main
import (
"log"
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Client struct {
Client *http.Client
Counter *prometheus.CounterVec
}
func NewClient(counter *prometheus.CounterVec) *Client {
return &Client{
Client: &http.Client{},
Counter: counter,
}
}
func (c *Client) Do(rqst *http.Request) (*http.Response, error) {
resp, err := c.Client.Do(rqst)
host := rqst.URL.Host
code := strconv.Itoa(resp.StatusCode)
c.Counter.With(prometheus.Labels{
"host": host,
"response_code": code,
}).Inc()
return resp, err
}
func main() {
metric := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "foo",
Help: "Number of HTTP requests.",
},
[]string{
"host",
"response_code",
},
)
prometheus.MustRegister(metric)
c := NewClient(metric)
go func() {
for {
method := http.MethodPost
url := "https://httpbin.org/post"
rqst, _ := http.NewRequest(method, url, nil)
_, _ = c.Do(rqst)
time.Sleep(15 * time.Second)
}
}()
log.Println("Starting server")
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))
}
And:
# HELP foo Number of HTTP requests.
# TYPE foo counter
foo{host="httpbin.org",method="POST",response_code="200"} 6```
Related
I am trying to turn off handling GET requests in golang.
I just want to handle POST.
Is it possible to do?
Reason for doing so is that i can see more and more memory being allocated by golang whenever i go to localhost:8080 and refresh page multiple times.
Here is my test code:
package main
import (
"fmt"
"net/http"
"encoding/json"
)
type test_struct struct {
Test string
}
var t test_struct
func handlePOST(rw http.ResponseWriter, req *http.Request) {
switch req.Method {
case "POST":
decoder := json.NewDecoder(req.Body)
decoder.Decode(&t)
defer req.Body.Close()
fmt.Println(t.Test)
}
}
func main() {
http.HandleFunc("/", handlePOST)
http.ListenAndServe(":8080", nil)
}
You cannot not handle GET requests, Go's HTTP server (or rather its http.ServeMux) only allows you to specify a path pattern before dispatching the request to your handler. HTTP method related routing can only happen at the handler level.
Note that some external mux libraries allow you to register handlers to specific HTTP methods only, but the decision and routing based on that also happens in "hidden" handlers of those libraries.
What you're doing is the best: simply do nothing in the handler if the HTTP method is not the one you intend to handle, or even better: send back a http.StatusMethodNotAllowed error response:
func myHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Only POST is allowed", http.StatusMethodNotAllowed)
return
}
var t test_struct // Use local var not global, else it's a data race
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&t); err != nil {
fmt.Println("Error decoding:", err)
}
fmt.Println(t.Test)
}
I would like to know how to properly implement/use context.Done() method when using it within an HTTP server and implementing middleware, my goal is to cancel subsequent events when a client disconnects across nested middleware.
For testing I created the following code, I don't know if is the correct way of doing it since I had to create a channel within the HandleFunc and a goroutine to handle the requests, putting all this together within a select wait statement.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func hello(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log.Println("handler started")
defer log.Println("hander ended")
ch := make(chan struct{})
go func() {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "Hello")
ch <- struct{}{}
}()
select {
case <-ch:
case <-ctx.Done():
err := ctx.Err()
log.Println(err)
http.Error(w, err.Error(), http.StatusPartialContent)
}
}
func main() {
http.HandleFunc("/", hello)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Basically here the request simulates load by sleeping 5 seconds, and then prints Hello, but if the client cancels the request, for example:
$ curl 0:8080
And then pressing ctl + c, this will be loged:
2017/07/07 22:22:40 handler started
2017/07/07 22:22:42 context canceled
2017/07/07 22:22:42 hander ended
This works but wondering if this pattern (the goroutine and select) should be used in every nested handler or if there is a better way of implementing this.:
ch := make(chan struct{})
go func() {
// some logic
ch <- struct{}{}
}()
select {
case <-ch:
case <-ctx.Done():
err := ctx.Err()
log.Println(err)
http.Error(w, err.Error(), http.StatusPartialContent)
}
At Google, we require that Go programmers pass a Context parameter as the first argument to every function on the call path between incoming and outgoing requests.
-- Go Concurrency Patterns: Context
Why can't I run both HTTP and HTTPS from the same golang program?
Here is the code where the two servers are initiated.. The server which is initiated first will run - the second won't.. If they are switched arround the other will run and the other won't..
No errors are returned when running the program, but the requests http://www.localhost or https://secure.localhost times out
// Start HTTP
err_http := http.ListenAndServe(fmt.Sprintf(":%d", port), http_r)
if err_http != nil {
log.Fatal("Web server (HTTP): ", err_http)
}
// Start HTTPS
err_https := http.ListenAndServeTLS(fmt.Sprintf(":%d", ssl_port), "D:/Go/src/www/ssl/public.crt", "D:/Go/src/www/ssl/private.key", https_r)
if err_https != nil {
log.Fatal("Web server (HTTPS): ", err_https)
}
Here is the complete code
package main
import (
"net/http"
"fmt"
"log"
"os"
"io"
"runtime"
// go get github.com/gorilla/mux
"github.com/gorilla/mux"
)
const (
HOST = "localhost"
)
func Handler_404(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Oops, something went wrong!")
}
func Handler_www(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello world :)")
}
func Handler_api(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "This is the API")
}
func Handler_secure(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "This is Secure")
}
func redirect(r *mux.Router, from string, to string){
r.Host(from).Subrouter().HandleFunc("/", func (w http.ResponseWriter, r *http.Request){
http.Redirect(w, r, to, 301)
})
}
func main(){
port := 9000
ssl_port := 443
runtime.GOMAXPROCS(runtime.NumCPU())
http_r := mux.NewRouter()
https_r := mux.NewRouter()
// HTTP 404
http_r.NotFoundHandler = http.HandlerFunc(Handler_404)
// Redirect "http://HOST" => "http://www.HOST"
redirect(http_r, HOST, fmt.Sprintf("http://www.%s:%d", HOST, port))
// Redirect "http://secure.HOST" => "https://secure.HOST"
redirect(http_r, "secure."+HOST, fmt.Sprintf("https://secure.%s", HOST))
www := http_r.Host("www."+HOST).Subrouter()
www.HandleFunc("/", Handler_www)
api := http_r.Host("api."+HOST).Subrouter()
api.HandleFunc("/", Handler_api)
secure := https_r.Host("secure."+HOST).Subrouter()
secure.HandleFunc("/", Handler_secure)
// Start HTTP
err_http := http.ListenAndServe(fmt.Sprintf(":%d", port), http_r)
if err_http != nil {
log.Fatal("Web server (HTTP): ", err_http)
}
// Start HTTPS
err_https := http.ListenAndServeTLS(fmt.Sprintf(":%d", ssl_port), "D:/Go/src/www/ssl/public.crt", "D:/Go/src/www/ssl/private.key", https_r)
if err_https != nil {
log.Fatal("Web server (HTTPS): ", err_https)
}
}
ListenAndServe and ListenAndServeTLS open the listening socket and then loop forever serving client connections. These functions only return on an error.
The main goroutine never gets to the starting the TLS server because the main goroutine is busy waiting for HTTP connections in ListenAndServe.
To fix the problem, start the HTTP server in a new goroutine:
// Start HTTP
go func() {
err_http := http.ListenAndServe(fmt.Sprintf(":%d", port), http_r)
if err_http != nil {
log.Fatal("Web server (HTTP): ", err_http)
}
}()
// Start HTTPS
err_https := http.ListenAndServeTLS(fmt.Sprintf(":%d", ssl_port), "D:/Go/src/www/ssl/public.crt", "D:/Go/src/www/ssl/private.key", https_r)
if err_https != nil {
log.Fatal("Web server (HTTPS): ", err_https)
}
As previously said, both ListenAndServe and ListenAndServeTLS are blocking. That being said, I would agree that examples above are in fact resolving your issue as the point is to be in goroutine BUT same examples are not quite following go idioms.
You should be using error channels here as you want to capture ALL errors that are sent to you instead of having just one error returned back. Here's fully working sample that starts HTTP as HTTPS servers and return errors as channel that's later on used just to display errors.
package main
import (
"log"
"net/http"
)
func Run(addr string, sslAddr string, ssl map[string]string) chan error {
errs := make(chan error)
// Starting HTTP server
go func() {
log.Printf("Staring HTTP service on %s ...", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
errs <- err
}
}()
// Starting HTTPS server
go func() {
log.Printf("Staring HTTPS service on %s ...", addr)
if err := http.ListenAndServeTLS(sslAddr, ssl["cert"], ssl["key"], nil); err != nil {
errs <- err
}
}()
return errs
}
func sampleHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("This is an example server.\n"))
}
func main() {
http.HandleFunc("/", sampleHandler)
errs := Run(":8080", ":10443", map[string]string{
"cert": "/path/to/cert.pem",
"key": "/path/to/key.pem",
})
// This will run forever until channel receives error
select {
case err := <-errs:
log.Printf("Could not start serving service due to (error: %s)", err)
}
}
Hope this helps! :)
func serveHTTP(mux *http.ServeMux, errs chan<- error) {
errs <- http.ListenAndServe(":80", mux)
}
func serveHTTPS(mux *http.ServeMux, errs chan<- error) {
errs <- http.ListenAndServeTLS(":443", "fullchain.pem", "privkey.pem", mux)
}
func main() {
mux := http.NewServeMux()
// setup routes for mux // define your endpoints
errs := make(chan error, 1) // a channel for errors
go serveHTTP(mux, errs) // start the http server in a thread
go serveHTTPS(mux, errs) // start the https server in a thread
log.Fatal(<-errs) // block until one of the servers writes an error
}
The ListenAndServe (and ListenAndServeTLS) functions do not return to their caller (unless an error is encountered). You can test this by trying to print something in between the two calls.
I'm currently writing some software in Go that interacts with a REST API. The REST API endpoint I'm trying to query returns an HTTP 302 redirect along with an HTTP Location header, pointing to a resource URI.
I'm trying to use my Go script to grab the HTTP Location header for later processing.
Here's what I'm currently doing to achieve this functionality:
package main
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
)
var BASE_URL = "https://api.example.com/v1"
var STORMPATH_API_KEY_ID = "xxx"
var STORMPATH_API_KEY_SECRET = "xxx"
func noRedirect(req *http.Request, via []*http.Request) error {
return errors.New("Don't redirect!")
}
func main() {
client := &http.Client{
CheckRedirect: noRedirect
}
req, err := http.NewRequest("GET", BASE_URL+"/tenants/current", nil)
req.SetBasicAuth(EXAMPLE_API_KEY_ID, EXAMPLE_API_KEY_SECRET)
resp, err := client.Do(req)
// If we get here, it means one of two things: either this http request
// actually failed, or we got an http redirect response, and should process it.
if err != nil {
if resp.StatusCode == 302 {
fmt.Println("got redirect")
} else {
panic("HTTP request failed.")
}
}
defer resp.Body.Close()
}
This feels like a bit of a hack to me. By overriding the http.Client's CheckRedirect function, I'm essentially forced to treat HTTP redirects like errors (which they aren't).
I've seen several other places suggesting to use an HTTP transport instead of an HTTP client -- but I'm not sure how to make this work since I need the HTTP Client as I need to use HTTP Basic Auth to communicate with this REST API.
Can any of you tell me a way to make HTTP requests with Basic Authentication -- while not following redirects -- that doesn't involve throwing errors and error handling?
There's a much simpler solution right now:
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
This way, the http package automatically knows: "Ah, I shouldn't follow any redirects", but does not throw any error. From the comment in the source code:
As a special case, if CheckRedirect returns ErrUseLastResponse,
then the most recent response is returned with its body
unclosed, along with a nil error.
Another option, using the client itself, without the RoundTrip:
// create a custom error to know if a redirect happened
var RedirectAttemptedError = errors.New("redirect")
client := &http.Client{}
// return the error, so client won't attempt redirects
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return RedirectAttemptedError
}
// Work with the client...
resp, err := client.Head(urlToAccess)
// test if we got the custom error
if urlError, ok := err.(*url.Error); ok && urlError.Err == RedirectAttemptedError{
err = nil
}
UPDATE: this solution is for go < 1.7
It is possible, but the solution inverts the problem a little. Here's a sample written up as a golang test.
package redirects
import (
"github.com/codegangsta/martini-contrib/auth"
"github.com/go-martini/martini"
"net/http"
"net/http/httptest"
"testing"
)
func TestBasicAuthRedirect(t *testing.T) {
// Start a test server
server := setupBasicAuthServer()
defer server.Close()
// Set up the HTTP request
req, err := http.NewRequest("GET", server.URL+"/redirect", nil)
req.SetBasicAuth("username", "password")
if err != nil {
t.Fatal(err)
}
transport := http.Transport{}
resp, err := transport.RoundTrip(req)
if err != nil {
t.Fatal(err)
}
// Check if you received the status codes you expect. There may
// status codes other than 200 which are acceptable.
if resp.StatusCode != 200 && resp.StatusCode != 302 {
t.Fatal("Failed with status", resp.Status)
}
t.Log(resp.Header.Get("Location"))
}
// Create an HTTP server that protects a URL using Basic Auth
func setupBasicAuthServer() *httptest.Server {
m := martini.Classic()
m.Use(auth.Basic("username", "password"))
m.Get("/ping", func() string { return "pong" })
m.Get("/redirect", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/ping", 302)
})
server := httptest.NewServer(m)
return server
}
You should be able to put the above code into it's own package called "redirects" and run it after fetching the required dependencies using
mkdir redirects
cd redirects
# Add the above code to a file with an _test.go suffix
go get github.com/codegangsta/martini-contrib/auth
go get github.com/go-martini/martini
go test -v
Hope this helps!
To make request with Basic Auth that does not follow redirect use RoundTrip function that accepts *Request
This code
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
var DefaultTransport http.RoundTripper = &http.Transport{}
req, _ := http.NewRequest("GET", "http://httpbin.org/headers", nil)
req.SetBasicAuth("user", "password")
resp, _ := DefaultTransport.RoundTrip(req)
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Printf("%s", err)
os.Exit(1)
}
fmt.Printf("%s\n", string(contents))
}
outputs
{
"headers": {
"Accept-Encoding": "gzip",
"Authorization": "Basic dXNlcjpwYXNzd29yZA==",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "Go 1.1 package http",
"X-Request-Id": "45b512f1-22e9-4e49-8acb-2f017e0a4e35"
}
}
As an addition of top rated answer,
You can control the particle size
func myCheckRedirect(req *http.Request, via []*http.Request, times int) error {
err := fmt.Errorf("redirect policy: stopped after %d times", times)
if len(via) >= times {
return err
}
return nil
}
...
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return myCheckRedirect(req, via, 1)
},
}
ref: https://golangbyexample.com/http-no-redirect-client-golang/
I want to stop the http server remotely (which I can do), but I also want to receive a message indicating that it has been stopped after it has been stopped. That is causing me some problems. The only solution that I could find is as shown below, which I do not consider ideal.
Can anyone provide a better solution. The problem is that the final message sent to the client is not getting through unless I use the goroutine as shown at the end "go func() {".
Code is as follows:
//*************
func stopServer(ohtWriter http.ResponseWriter, phtRequest *http.Request) {// Stop The Server
//*************
var iBytesSent int
var oOsError os.Error
var sErmes string
println("Stopping Server")
iBytesSent,oOsError = ohtWriter.Write([]byte("Message from server - server now stopped."))
if oOsError != nil {
sErmes = ". Error = " +oOsError.String()
} else {
sErmes = ". No error on write"
}
println("stopServer: Bytes sent = " +strconv.Itoa(iBytesSent) +sErmes)
ohtFlusher, tCanFlush := ohtWriter.(http.Flusher)
if tCanFlush {
ohtFlusher.Flush()
}
go func() {
time.Sleep(3e9)
os.Exit(0)
}()
}
Yeah I think without support from the http package, a graceful shutdown is not really possible. This is maybe a little bit less cringe-worthy, but will still slam closed any other concurrent requests in flight at the time of this request. Maybe try filing a feature request on the Go issue tracker. Better yet, open up the http package, and add a graceful shutdown method, and submit it.
Edit: I guess if you control all of the http.Handlers in your app, you could keep a count of in-flight requests (using appropriate thread synchronization), and modify the code below to a) refuse new connections once "shutdown" is called and b) wait for all in-flight requests to complete before shutting down...
package main
import (
"http"
"os"
"io"
"log"
"strconv"
)
func main() {
http.HandleFunc("/", ServeHTTP)
http.ListenAndServe(":8081", nil)
}
const responseString = "Shutting down\n"
func ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Content-Length", strconv.Itoa(len(responseString)))
io.WriteString(w, responseString)
f, canFlush := w.(http.Flusher)
if canFlush {
f.Flush()
}
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
log.Fatalf("error while shutting down: %v", err)
}
conn.Close()
log.Println("Shutting down")
os.Exit(0)
}
Haven't tried it yet, but using http.ServerConn directly might work.
Here's a simple way that's good enough for local development.
http://www.sergiotapia.me/how-to-stop-your-go-http-server/
package main
import (
"net/http"
"os"
"github.com/bmizerany/pat"
)
var mux = pat.New()
func main() {
mux.Get("/kill", http.HandlerFunc(kill))
http.Handle("/", mux)
http.ListenAndServe(":8080", nil)
}
func kill(w http.ResponseWriter, r *http.Request) {
os.Exit(0)
}