Los conceptos básicos de un servidor web Go

Go es un lenguaje de programación orientado a la red y, de hecho, la biblioteca estándar tiene unanet/httpmódulo, y el libro "The Go Programming Language" de Donovan y Kernighan tiene un ejemplo de servidor weben el primer capitulo:

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

package main

import ( “fmt” “log” “net/http” )

func main() { http.HandleFunc("/", handler) // each request calls handler log.Fatal(http.ListenAndServe(“localhost:8000”, nil)) }

// handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) }

Esta es la forma más básica de servidor HTTP.

Escribego run filename.goy el servidor está activo, aceptando solicitudes entrantes.

loshandlerLa función toma una interfaz ResponseWriter, que nos permite escribirle al cliente y los detalles de la solicitud HTTP.

http.Requestmuestra mucha información sobre la solicitud entrante:

type Request struct {
        // Method specifies the HTTP method (GET, POST, PUT, etc.).
        // For client requests an empty string means GET.
        Method string
    <span style="color:#75715e">// URL specifies either the URI being requested (for server

// requests) or the URL to access (for client requests). // // For server requests the URL is parsed from the URI // supplied on the Request-Line as stored in RequestURI. For // most requests, fields other than Path and RawQuery will be // empty. (See RFC 2616, Section 5.1.2) // // For client requests, the URL’s Host specifies the server to // connect to, while the Request’s Host field optionally // specifies the Host header value to send in the HTTP // request. URL *url.URL

    <span style="color:#75715e">// The protocol version for incoming server requests.

// // For client requests these fields are ignored. The HTTP // client code always uses either HTTP/1.1 or HTTP/2. // See the docs on Transport for details. Proto string // “HTTP/1.0” ProtoMajor int // 1 ProtoMinor int // 0 // Header contains the request header fields either received // by the server or to be sent by the client. // // If a server received a request with header lines, // // Host: example.com // accept-encoding: gzip, deflate // Accept-Language: en-us // fOO: Bar // foo: two // // then // // Header = map[string][]string{ // “Accept-Encoding”: {“gzip, deflate”}, // “Accept-Language”: {“en-us”}, // “Foo”: {“Bar”, “two”}, // } // // For incoming requests, the Host header is promoted to the // Request.Host field and removed from the Header map. // // HTTP defines that header names are case-insensitive. The // request parser implements this by using CanonicalHeaderKey, // making the first character and any characters following a // hyphen uppercase and the rest lowercase. // // For client requests, certain headers such as Content-Length // and Connection are automatically written when needed and // values in Header may be ignored. See the documentation // for the Request.Write method. Header Header

    <span style="color:#75715e">// Body is the request's body.

// // For client requests a nil body means the request has no // body, such as a GET request. The HTTP Client’s Transport // is responsible for calling the Close method. // // For server requests the Request Body is always non-nil // but will return EOF immediately when no body is present. // The Server will close the request body. The ServeHTTP // Handler does not need to. Body io.ReadCloser

    <span style="color:#75715e">// GetBody defines an optional func to return a new copy of

// Body. It is used for client requests when a redirect requires // reading the body more than once. Use of GetBody still // requires setting Body. // // For server requests it is unused. GetBody func() (io.ReadCloser, error)

    <span style="color:#75715e">// ContentLength records the length of the associated content.

// The value -1 indicates that the length is unknown. // Values >= 0 indicate that the given number of bytes may // be read from Body. // For client requests, a value of 0 with a non-nil Body is // also treated as unknown. ContentLength int64

    <span style="color:#75715e">// TransferEncoding lists the transfer encodings from outermost to

// innermost. An empty list denotes the “identity” encoding. // TransferEncoding can usually be ignored; chunked encoding is // automatically added and removed as necessary when sending and // receiving requests. TransferEncoding []string

    <span style="color:#75715e">// Close indicates whether to close the connection after

// replying to this request (for servers) or after sending this // request and reading its response (for clients). // // For server requests, the HTTP server handles this automatically // and this field is not needed by Handlers. // // For client requests, setting this field prevents re-use of // TCP connections between requests to the same hosts, as if // Transport.DisableKeepAlives were set. Close bool

    <span style="color:#75715e">// For server requests Host specifies the host on which the

// URL is sought. Per RFC 2616, this is either the value of // the “Host” header or the host name given in the URL itself. // It may be of the form “host:port”. For international domain // names, Host may be in Punycode or Unicode form. Use // golang.org/x/net/idna to convert it to either format if // needed. // // For client requests Host optionally overrides the Host // header to send. If empty, the Request.Write method uses // the value of URL.Host. Host may contain an international // domain name. Host string

    <span style="color:#75715e">// Form contains the parsed form data, including both the URL

// field’s query parameters and the POST or PUT form data. // This field is only available after ParseForm is called. // The HTTP client ignores Form and uses Body instead. Form url.Values

    <span style="color:#75715e">// PostForm contains the parsed form data from POST, PATCH,

// or PUT body parameters. // // This field is only available after ParseForm is called. // The HTTP client ignores PostForm and uses Body instead. PostForm url.Values

    <span style="color:#75715e">// MultipartForm is the parsed multipart form, including file uploads.

// This field is only available after ParseMultipartForm is called. // The HTTP client ignores MultipartForm and uses Body instead. MultipartForm *multipart.Form

    <span style="color:#75715e">// Trailer specifies additional headers that are sent after the request

// body. // // For server requests the Trailer map initially contains only the // trailer keys, with nil values. (The client declares which trailers it // will later send.) While the handler is reading from Body, it must // not reference Trailer. After reading from Body returns EOF, Trailer // can be read again and will contain non-nil values, if they were sent // by the client. // // For client requests Trailer must be initialized to a map containing // the trailer keys to later send. The values may be nil or their final // values. The ContentLength must be 0 or -1, to send a chunked request. // After the HTTP request is sent the map values can be updated while // the request body is read. Once the body returns EOF, the caller must // not mutate Trailer. // // Few HTTP clients, servers, or proxies support HTTP trailers. Trailer Header

    <span style="color:#75715e">// RemoteAddr allows HTTP servers and other software to record

// the network address that sent the request, usually for // logging. This field is not filled in by ReadRequest and // has no defined format. The HTTP server in this package // sets RemoteAddr to an “IP:port” address before invoking a // handler. // This field is ignored by the HTTP client. RemoteAddr string

    <span style="color:#75715e">// RequestURI is the unmodified Request-URI of the

// Request-Line (RFC 2616, Section 5.1) as sent by the client // to a server. Usually the URL field should be used instead. // It is an error to set this field in an HTTP client request. RequestURI string

    <span style="color:#75715e">// TLS allows HTTP servers and other software to record

// information about the TLS connection on which the request // was received. This field is not filled in by ReadRequest. // The HTTP server in this package sets the field for // TLS-enabled connections before invoking a handler; // otherwise it leaves the field nil. // This field is ignored by the HTTP client. TLS *tls.ConnectionState

    <span style="color:#75715e">// Cancel is an optional channel whose closure indicates that the client

// request should be regarded as canceled. Not all implementations of // RoundTripper may support Cancel. // // For server requests, this field is not applicable. // // Deprecated: Use the Context and WithContext methods // instead. If a Request’s Cancel field and context are both // set, it is undefined whether Cancel is respected. Cancel <-chan struct{}

    <span style="color:#75715e">// Response is the redirect response which caused this request

// to be created. This field is only populated during client // redirects. Response *Response // contains filtered or unexported fields }

En este caso, nos interesar.URL, una estructura de URL definida en el paquete net.url:

type URL struct {
        Scheme     string
        Opaque     string    // encoded opaque data
        User       *Userinfo // username and password information
        Host       string    // host or host:port
        Path       string
        RawPath    string // encoded path hint (Go 1.5 and later only; see EscapedPath method)
        ForceQuery bool   // append a query ('?') even if RawQuery is empty
        RawQuery   string // encoded query values, without '?'
        Fragment   string // fragment for references, without '#'
}

r.URL.Pathimprime la ruta solicitada actualmente, por lo que, en resumen, el servidor web que acabamos de escribir en este momento es un simple eco de la URL de la solicitud.


Controladores de solicitudes múltiples

¿Cómo puede configurar un segundo controlador de solicitudes para una ruta específica y dejar que el controlador original administre todas las demás solicitudes de ruta?

package main

import ( “fmt” “log” “sync” )

func main() { http.HandleFunc("/", handler) http.HandleFunc("/about", aboutHandler) log.Fatal(http.ListenAndServe(“localhost:8000”, nil)) }

// handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) }

// counter echoes the number of calls so far. func aboutHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) //… }

Aquí todas las solicitudes paraalgunaLas URL todavía son manejadas porhandler(), excepto/count. Esto se debe a que al pasar elpatternparámetro que termina con/ahttp.HandleFunc()causará una coincidencia de todas las subrutas, excepto que se encuentre una más especializada.

Accediendo a los recursos

El segundo ejemplo de servidor web proporciona una forma para que los controladores administren recursos comunes en un entorno de concurrencia.

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

package main

import ( “fmt” “log” “net/http” “sync” )

var mu sync.Mutex var count int

func main() { http.HandleFunc("/", handler) http.HandleFunc("/count", counter) log.Fatal(http.ListenAndServe(“localhost:8000”, nil)) }

// handler echoes the Path component of the requested URL. func handler(w http.ResponseWriter, r *http.Request) { mu.Lock() count++ mu.Unlock() fmt.Fprintf(w, “URL.Path = %q\n”, r.URL.Path) }

// counter echoes the number of calls so far. func counter(w http.ResponseWriter, r *http.Request) { mu.Lock() fmt.Fprintf(w, “Count %d\n”, count) mu.Unlock() }

Este ejemplo usa un Mutex, porque internamentehttp.HandleFun()usosgorutinaspara despedir a los manipuladores de solicitudes, yhandler()La función incrementa una variable de paquete. Para evitar uncondición de carrerade suceder, debemos llamar a Mutex.Lock antes de cambiar su valor (lo mismo ocurre concounter()al imprimir su valor).

Imprimir los encabezados de la solicitud

El libro también presenta un controlador de muestra cuyo trabajo es imprimir los encabezados de solicitud, bien formateados:

// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

// handler echoes the HTTP request.
func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s %s %s\n", r.Method, r.URL, r.Proto)
	for k, v := range r.Header {
		fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
	}
	fmt.Fprintf(w, "Host = %q\n", r.Host)
	fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)
	if err := r.ParseForm(); err != nil {
		log.Print(err)
	}
	for k, v := range r.Form {
		fmt.Fprintf(w, "Form[%q] = %q\n", k, v)
	}
}

Abrir el servidor en el navegador dará como resultado este resultado, por ejemplo:

GET / HTTP/1.1
Header["Connection"] = ["keep-alive"]
Header["Accept"] = ["text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"]
Header["Accept-Language"] = ["en-us"]
Header["Accept-Encoding"] = ["gzip, deflate"]
Header["Upgrade-Insecure-Requests"] = ["1"]
Header["User-Agent"] = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30"]
Header["Dnt"] = ["1"]
Host = "localhost:8000"
RemoteAddr = "127.0.0.1:51774"

Más tutoriales de go: