Web програмиране

13.12.2016

Въпроси за мъфини

Въпрос за мъфин #1

Какво трябва да направите до 24-ти декември

Въпрос за мъфин #2

Какво ще върне recover(), ако го извикаме без да сме в състояние на паника?

Въпрос за мъфин #3

Какво ще изведе този код:

x := []string{"a","b","c"}

for v := range x {
  fmt.Println(v)
}

Въпрос за мъфин #4

Какво прави go test ./...?

Въпрос за мъфин #5

Какво е example test и как се прави такъв?

TCP/IP

Коректна имплементация е трудна

conn, err := net.Dial("tcp", "golang.org:80")
if err != nil {
    // handle error
}
fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
status, err := bufio.NewReader(conn).ReadString('\n')

`net` е ваш приятел

net.Conn

net.Conn implementations

net.Dial

func Dial(network, address string) (Conn, error)

net.Addr

net.Listen()

Пример

ln, err := net.Listen("tcp", ":8080")
if err != nil {
    // handle error
}
for {
    conn, err := ln.Accept()
    if err != nil {
        // handle error
    }
    go handleConnection(conn)
}

Няколко други интересни дреболии

HTTP

Протокола

Версии

Всичките? Едновременно?

В Go си имаме библиотека

Защо!?

Клиентска част

package main

import (
    "fmt"
    "net/http"
)

func main() {
    resp, err := http.Get("http://fmi.golang.bg/")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("GET fmi golang site said HTTP %d\n", resp.StatusCode)
}

http.Request

type Request struct {
    // Method specifies the HTTP method (GET, POST, PUT, etc.).
    Method string

    // URL specifies either the URI being requested or the URL to access.
    URL *url.URL

    // A header maps request lines to their values.
    Header Header

    // И доста други полета, за някои от които ще разкажем след малко...
}

http.Client

Do(req *Request) (resp *Response, err error)

Чрез атрибутите на http.Client имате възможност да определите:

http.Response

Тялото на отговора може да се чете от Body (io.ReadCloser):

Интересна част

package main

import (
	"fmt"
	"html"
	"io/ioutil"
	"log"
	"net/http"
)

func main() {
    go http.ListenAndServe(":8282", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s", html.EscapeString(r.URL.Path))
    }))

    res, err := http.Get("http://localhost:8282/world")
    if err != nil {
        log.Fatal(err)
    }
    contents, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Server responded with: %s", contents)
}

Получаване на клиентски заявки

err := req.ParseForm()
// handle if err != nil
sortOrder := req.Form.Get("sortby")

Повече от един начин да одереш котка:

http.ResponseWriter

type ResponseWriter interface {
    // Header returns the header map that will be sent by WriteHeader.
    Header() Header

    // Write writes the data to the connection as part of an HTTP reply.
    Write([]byte) (int, error)

    // WriteHeader sends an HTTP response header with status code.
    WriteHeader(int)
}

Това ни позволява да си използваме неща като:

fmt.Fprintf(rw, "Нещо си")

за да отговаряме на HTTP заявки...

http.Handler

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)

Mux - що е то?

Стандартни Handler-и

Пример

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"time"
)

func main() {
    mux := http.NewServeMux()
    mux.Handle("/doc/", http.FileServer(http.Dir("/usr/share/")))
    mux.Handle("/google/", http.RedirectHandler("https://www.google.com", http.StatusTemporaryRedirect))
    mux.Handle("/proxy/", http.StripPrefix("/proxy/", httputil.NewSingleHostReverseProxy(
        &url.URL{Scheme: "https", Host: "mirrors.kernel.org", Path: "/"},
    )))

    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        if req.URL.Path == "/" {
            fmt.Fprintf(w, "Welcome to the jungle!")
            return
        }
        http.NotFound(w, req)
    })

    s := &http.Server{Addr: ":8282", Handler: mux, WriteTimeout: 1 * time.Second}
    log.Printf("Starting server on %s", s.Addr)
    log.Fatal(s.ListenAndServe())
}

ResponseWriter допълнения

ResponseWriter е базов интерфейс, но в някои ситуации ви се налага да имате повече контрол:

type CloseNotifier interface {
    // CloseNotify returns a channel that receives a single value
    // when the client connection has gone away.
    CloseNotify() <-chan bool
}

type Hijacker interface {
    // Hijack lets the caller take over the connection.
    // After a call to Hijack(), the HTTP server library
    // will not do anything else with the connection.
    Hijack() (net.Conn, *bufio.ReadWriter, error)
}

http.RoundTripper и http.Transport

http.RoundTripper е абстракцията за изпълнение на единична HTTP request-response транзакция:

type RoundTripper interface {
    RoundTrip(*Request) (*Response, error)
}

http.Transport е имплементация на http.RoundTripper, която поддръжа HTTP, HTTPS и проксита. По подразбиране в http пакета имаме:

var DefaultTransport RoundTripper = &Transport{
    Proxy: ProxyFromEnvironment,
    Dial: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
    }).Dial,
    TLSHandshakeTimeout: 10 * time.Second,
}

Разширяване и тестване

http.Pusher (in 1.8)

Пример за push (in 1.8)

http.Handle("/index.html", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // server push is available if w implements http.Pusher
    if p, ok := w.(http.Pusher); ok {
        p.Push("/static/gopher.png", nil}
    }
    // load the main page
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte(`<img src="/static/gopher.png" />`))
}))

Темплейти

Пример

Темплейт:

<h1>Editing {{.Title}}</h1>

<form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>

Изполването му:

func editHandler(w http.ResponseWriter, r *http.Request) {
    p := &Page{
        Title: "Hello, world!",
        Body: "Lorem ipsum dolor sit amet...",
    }
    t, _ := template.ParseFiles("edit.html")
    t.Execute(w, p)
}

text/template vs html/template

text/template

import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")



Hello, <script>alert('you have been pwned')</script>!

html/template

import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")



Hello, &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!

Сигурност и escaping

Context                          {{.}} After
{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'>                O&#39;Reilly: How are you?
<a href="/{{.}}">                O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}">              O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'>             O\x27Reilly: How are \x3ci\x3eyou...?
<a onx='f({{.}})'>               "O\x27Reilly: How are \x3ci\x3eyou...?"
<a onx='pattern = /{{.}}/;'>     O\x27Reilly: How are \x3ci\x3eyou...\x3f

Gorilla

Няколко библиотеки, които ще улеснят живота ви по темата

context

Защо ни е context?

Как се ползва?

Пример

Въпроси?