Golang Web Development (1)

Main benefits

1.1 Cross-platform deployment at record speed

For enterprises, Go can provide rapid cross-platform deployment. With its goroutines, native compilation, and uri-based package namespace, Go code is compiled into a single, small binary (zero dependencies), which makes it very fast.

1.2 Take advantage of Go's out-of-the-box performance to easily expand

  • Compiles into one binary - "Using static linking, Go actually combines all dependent libraries and modules into one binary based on OS type and architecture."
  • Static type system - "Type systems are very important for large-scale applications."
  • Performance - Go performs better because of its concurrency model and CPU scalability. Whenever we need to process some internal requests, we use a separate goroutine to handle them, which saves 10 times the resources compared to Python threads. "
  • No need for a web framework - "In most cases, you don't really need any third-party libraries."
  • Excellent IDE support and debugging - "After rewriting all projects to Go, we got 64% less code than before."

Learn Go Web Programming by Example

Go by Example

Tutorials

About the Go programming language

Go is an open source programming language designed to build simple, fast and reliable software. See which great companies use Go to enhance their services.

Go Web Examples provides easy-to-follow code snippets on how to do web development in the Go programming language. It was inspired by Go by Example , which has a good introduction to the basics of the language.

If you're learning web programming with Go, or just getting started, you can find some great examples and tutorials here. Our goal is to provide clean examples with great detail so you can become the next Go web developer! Go Web Examples cover the basics of web programming. From routers and templating to middleware and websockets. Here you can find everything from clean code snippets to detailed tutorials.

First, see how to create a first example of a classic "Hello World" web application , or jump right into routing (using the gorilla/mux router) .

1、Hello World

1.1 Introduction

Go already has a built-in web server. The net/http package from the standard library contains all functions related to the HTTP protocol. This includes (among many other things) an HTTP client and an HTTP server. In this example, you'll see how easy it is to create a web server that can be viewed in a browser.

1.2 Register request handler

First, create a Handler to receive all incoming HTTP connections from browsers, HTTP clients, or API requests. A handler in Go is a function with the following signature:

func (w http.ResponseWriter, r *http.Request)

The function takes two parameters:
one is where http.ResponseWriteryou write the text/html response.
One http.Request, which contains all the information about this HTTP request, including URL or header fields.
Registering a request handler with the default HTTP server is as simple as:

http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
    
    
    fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
})

1.3 Listening for HTTP connections

The request handler itself cannot accept any HTTP connections from outside. The HTTP server must listen on the port in order to pass the connection to the request handler. Since port 80 is the default port for HTTP communication in most cases, this server listens on it as well .

The code below will start Go's default HTTP server and listen for connections on port 80. You can navigate your browser to http://localhost/and see the server processing your request.

http.ListenAndServe(":80", nil)

1.4 The Code (for copy/paste)

Here is the complete code that you can use to test what you have learned in this example.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    
    
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    
    
        fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
    })

    http.ListenAndServe(":80", nil)
}

2、HTTP Server

2.1 Introduction

In this example, you will learn how to create a basic HTTP server in Go. First, let's discuss what features an HTTP server should have. A basic HTTP server has several key tasks to handle.

  • Handle Dynamic Requests: Handle incoming requests from users browsing the site, logging into their accounts, or posting images.
  • Serve static assets: Serve JavaScript, CSS, and images to the browser to create a dynamic experience for users.
  • Accepting Connections: An HTTP server must listen on a specific port in order to be able to accept connections from the Internet.

2.2 Handling dynamic requests

The net/http package contains all the utilities needed to accept requests and process them dynamically. We can register a new handler with http. HandleFunc function. Its first parameter accepts a path to match and the second parameter is a function to execute. In this example: when someone visits your website ( http://example.com/), he or she gets a nice message.

http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
    
    
    fmt.Fprint(w, "Welcome to my website!")
})

For dynamic aspects, http.Requestcontains all information about the request and its parameters. You can r.URL.Query().Get("token")read GET parameters with , or r.FormValue("email")read POST parameters (fields from HTML forms).

2.3 Serving static resources

To serve static resources like JavaScript, CSS, and images, we use the built-in http.FileServerand point it to the url path. In order for a file server to work properly, it needs to know where to serve files from . We can do this:

fs := http.FileServer(http.Dir("static/"))

Once our file server is in place, we just need to point a url path to it, like we do for dynamic requests. One thing to note: in order to serve the file correctly, we need to strip part of the url path. Usually this is the name of the directory where our files are located.

http.Handle("/static/", http.StripPrefix("/static/", fs))

2.4 Accept connection

The last thing to complete a basic HTTP server is a listening port to accept connections from the internet. As you can guess, Go also has a built-in HTTP server, which we can quickly start. Once started, the HTTP server can be viewed in the browser.

http.ListenAndServe(":80", nil)

The Code

package main

import (
    "fmt"
    "net/http"
)

func main() {
    
    
    http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
    
    
        fmt.Fprintf(w, "Welcome to my website!")
    })

    fs := http.FileServer(http.Dir("static/"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    http.ListenAndServe(":80", nil)
}

3、Routing (using gorilla/mux)

3.1 Introduction

Go's net/http package provides a lot of functionality for the HTTP protocol. One thing it doesn't do very well is complex request routing, like splitting the request url into individual parameters. Fortunately, there is a very popular package that is known in the Go community for its good code quality. In this example, you will see how to use the gorilla/mux package to create routes with named parameters, GET/POST handlers, and domain restrictions .

3.2 Install the gorilla/mux package

gorilla/mux is 一个适应Go默认HTTP路由器的包. It provides many features to increase productivity when writing web applications. It is also compatible with Go's default request handler signature func (w http.ResponseWriter, r *http.Request), so the package can be mixed and matched with other HTTP libraries such as middleware or existing applications. Install the package from GitHub using go getthe command as follows:

3.3 Create a new route

First create a new request router. The router is the main router of the web application, which will be passed to the server later as a parameter. It will receive all HTTP connections and pass them on to the request handler you will register on it. You can create a new router like this:

r := mux.NewRouter()

3.4 Registering a Request Handler

Once you have a new router, you can register request handlers as usual. The only difference is, instead of calling , it is called on the router http.HandleFunc(...)like thisr.HandleFunc(...)HandleFunc:

3.5 URL Parameters

gorilla/muxThe biggest advantage of the router is the ability to extract parameters (segments) from the request URL. For example, this is a URL in the application:

/books/go-programming-blueprint/page/10

This URL has two dynamic parameters (segments):

  • Title section (go-programming-blueprint)
  • Page (10)

In order for a request handler to match the URLs mentioned above, you can replace dynamic segments with placeholders in your URL patterns like this:

r.HandleFunc("/books/{title}/page/{page}", func(w http.ResponseWriter, r *http.Request) {
    
    
    // get the book
    // navigate to the page
})

The last thing is to get the data from these fragments. This package comes with functions mux.Vars(r)that accept http.Requestas arguments and return a map of segments.

func(w http.ResponseWriter, r *http.Request) {
    
    
    vars := mux.Vars(r)
    vars["title"] // the book title slug
    vars["page"] // the page
}

3.6 Setting up the router for the HTTP server

Ever wondered what ishttp.ListenAndServe(":80", nil) ? It is a parameter of the main router of the HTTP server. nilBy default, it is nil, which means using net/httpthe package's default router. To use your own router, nilreplace the variable with the router r.

http.ListenAndServe(":80", r)

code

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    
    
    r := mux.NewRouter()

    r.HandleFunc("/books/{title}/page/{page}", func(w http.ResponseWriter, r *http.Request) {
    
    
        vars := mux.Vars(r)
        title := vars["title"]
        page := vars["page"]

        fmt.Fprintf(w, "You've requested the book: %s on page %s\n", title, page)
    })

    http.ListenAndServe(":80", r)
}

3.7 Features of gorilla/mux router

Methods

Limit request handlers to specific HTTP methods.

r.HandleFunc("/books/{title}", CreateBook).Methods("POST")
r.HandleFunc("/books/{title}", ReadBook).Methods("GET")
r.HandleFunc("/books/{title}", UpdateBook).Methods("PUT")
r.HandleFunc("/books/{title}", DeleteBook).Methods("DELETE")

hostname and subdomain

Restrict request handlers to specific hostnames or subdomains.

r.HandleFunc("/books/{title}", BookHandler).Host("www.mybookstore.com")

Schemes

Limit request handlers to http/https.

r.HandleFunc("/secure", SecureHandler).Schemes("https")
r.HandleFunc("/insecure", InsecureHandler).Schemes("http")

Path Prefixes & Subrouters

bookrouter := r.PathPrefix("/books").Subrouter()
bookrouter.HandleFunc("/", AllBooks)
bookrouter.HandleFunc("/{title}", GetBook)

4、MySQL Database

4.1 Introduction

At some point in time, you want your web application to store and retrieve data from the database. This is almost always the case when dealing with dynamic content, providing forms for users to enter data, or storing login and password credentials for user authentication. For this we have databases.

Databases come in all shapes and shapes. A commonly used database on all webs is the MySQL database. It's been around for a long time and has proven its stature and stability more times than you can count.

In this example, we will delve into the fundamentals of database access in Go, creating database tables, storing data and retrieving data again.

4.2 Install go-sql-driver/mysql package

The Go programming language provides a database/sqlconvenience package called "" that can query various sql databases. This is useful because it abstracts all common SQL features into an API for you to use. What Go doesn't include are database drivers . In Go, a database driver is a package that implements the low-level details of a particular database (MySQL in our case). As you might have guessed, this is great for maintaining forward compatibility. Because, at the time all Go packages were created, the authors could not foresee every database in the future, and supporting every possible database would be a massive maintenance effort.

To install the MySQL database driver, go to a terminal of your choice and run:

go get -u github.com/go-sql-driver/mysql

4.3 Connect to MySQL database

After installing all the necessary packages, the first thing we need to check is whether we can successfully connect to our MySQL database. If you don't have a MySQL database server running, you can easily spin up a new instance with Docker. Here is the official documentation of the Docker MySQL image: https://hub.docker.com/_/mysql

To check if we can connect to our database, import database/sqland go-sql-driver/mysqlpackage and open a connection like this:

import "database/sql"
import _ "go-sql-driver/mysql"


// Configure the database connection (always check errors)
db, err := sql.Open("mysql", "username:password@(127.0.0.1:3306)/dbname?parseTime=true")



// Initialize the first connection to the database, to see if everything works correctly.
// Make sure to check the error.
err := db.Ping()

4.4 Create the first database table

Each data entry in a database is stored in a specific table. A database table consists of columns and rows. Column gives each data item a label and specifies its type. Rows are the inserted data values. In the first example, we want to create a table like this:

id username password created_at
1 johndoe secret 2019-08-10 12:30:00

Translated into SQL, the command to create a table is as follows:

CREATE TABLE users (
    id INT AUTO_INCREMENT,
    username TEXT NOT NULL,
    password TEXT NOT NULL,
    created_at DATETIME,
    PRIMARY KEY (id)
);

Now that we have the SQL command, we can use database/sqlthe package to create a table in the MySQL database:

query := `
    CREATE TABLE users (
        id INT AUTO_INCREMENT,
        username TEXT NOT NULL,
        password TEXT NOT NULL,
        created_at DATETIME,
        PRIMARY KEY (id)
    );`

// Executes the SQL query in our database. Check err to ensure there was no error.
_, err := db.Exec(query)

4.5 Inserting our first user

If you are familiar with SQL, inserting new data into a table is as easy as creating the table. One thing to note: By default, Go uses prepared statementsdynamic data insertion into SQL queries, which is a way to safely pass user-supplied data into the database without any risk of corruption . In the early days of web programming, programmers passed data directly to the database along with queries, which led to a large number of vulnerabilities and could break the entire web application. Please don't do this. It's easy to do.

To insert the first user into the database table, we create the SQL query as shown below. As you can see, we omitted the id column since it was set automatically by MySQL. The question marks tell the SQL driver that they are placeholders for actual data. Here you can see what we discussed prepared statements.

INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)

Now we can use this SQL query in Go and insert a new row into our table:

import "time"

username := "johndoe"
password := "secret"
createdAt := time.Now()

// Inserts our data into the users table and returns with the result and a possible error.
// The result contains information about the last inserted id (which was auto-generated for us) and the count of rows this query affected.
result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)

To get the newly created user id, just get it like this:

userID, err := result.LastInsertId()

4.6 Query user table

Now that we have a user in our table, we want to query it and retrieve all of its information. In Go, we have two ways to query a table: one db.Query, if we can query multiple rows for us to iterate; one db.QueryRow, if we only want to query specific rows.

Querying for specific rows works basically the same as the other SQL commands we've covered before.
The SQL command to query a single user by ID is as follows:

SELECT id, username, password, created_at FROM users WHERE id = ?

In Go, we start by declaring some variables to store the data, then query a single database row like this:

var (
    id        int
    username  string
    password  string
    createdAt time.Time
)

// Query the database and scan the values into out variables. Don't forget to check for errors.
query := `SELECT id, username, password, created_at FROM users WHERE id = ?`
err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt)

4.7 Querying all users

In the previous section, we covered how to query individual user rows. Many applications have use cases where all existing users need to be queried. This is similar to the example above, but involves more coding.

We can use the SQL command in the example above and drop WHEREthe clause. In this way we can query for all existing users:

SELECT id, username, password, created_at FROM users

In Go, we start by declaring some variables to store the data, then query a single database row like this:

type user struct {
    
    
    id        int
    username  string
    password  string
    createdAt time.Time
}

rows, err := db.Query(`SELECT id, username, password, created_at FROM users`) // check err
defer rows.Close()

var users []user
for rows.Next() {
    
    
    var u user
    err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt) // check err
    users = append(users, u)
}
err := rows.Err() // check err

A user slice may now contain something like this:

users {
    
    
    user {
    
    
        id:        1,
        username:  "johndoe",
        password:  "secret",
        createdAt: time.Time{
    
    wall: 0x0, ext: 63701044325, loc: (*time.Location)(nil)},
    },
    user {
    
    
        id:        2,
        username:  "alice",
        password:  "bob",
        createdAt: time.Time{
    
    wall: 0x0, ext: 63701044622, loc: (*time.Location)(nil)},
    },
}

4.8 Delete a user from the table

Finally, deleting a user from our table is as .Execsimple as in the section above:

_, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1) // check err

The whole code is as follows:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    
    
    db, err := sql.Open("mysql", "root:root@(127.0.0.1:3306)/root?parseTime=true")
    if err != nil {
    
    
        log.Fatal(err)
    }
    if err := db.Ping(); err != nil {
    
    
        log.Fatal(err)
    }

    {
    
     // Create a new table
        query := `
            CREATE TABLE users (
                id INT AUTO_INCREMENT,
                username TEXT NOT NULL,
                password TEXT NOT NULL,
                created_at DATETIME,
                PRIMARY KEY (id)
            );`

        if _, err := db.Exec(query); err != nil {
    
    
            log.Fatal(err)
        }
    }

    {
    
     // Insert a new user
        username := "johndoe"
        password := "secret"
        createdAt := time.Now()

        result, err := db.Exec(`INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)`, username, password, createdAt)
        if err != nil {
    
    
            log.Fatal(err)
        }

        id, err := result.LastInsertId()
        fmt.Println(id)
    }

    {
    
     // Query a single user
        var (
            id        int
            username  string
            password  string
            createdAt time.Time
        )

        query := "SELECT id, username, password, created_at FROM users WHERE id = ?"
        if err := db.QueryRow(query, 1).Scan(&id, &username, &password, &createdAt); err != nil {
    
    
            log.Fatal(err)
        }

        fmt.Println(id, username, password, createdAt)
    }

    {
    
     // Query all users
        type user struct {
    
    
            id        int
            username  string
            password  string
            createdAt time.Time
        }

        rows, err := db.Query(`SELECT id, username, password, created_at FROM users`)
        if err != nil {
    
    
            log.Fatal(err)
        }
        defer rows.Close()

        var users []user
        for rows.Next() {
    
    
            var u user

            err := rows.Scan(&u.id, &u.username, &u.password, &u.createdAt)
            if err != nil {
    
    
                log.Fatal(err)
            }
            users = append(users, u)
        }
        if err := rows.Err(); err != nil {
    
    
            log.Fatal(err)
        }

        fmt.Printf("%#v", users)
    }

    {
    
    
        _, err := db.Exec(`DELETE FROM users WHERE id = ?`, 1)
        if err != nil {
    
    
            log.Fatal(err)
        }
    }
}

5、templates

5.1 Introduction

The Go html/templatepackage provides a rich templating language for HTML templates. It is mainly used in web applications to display data in the client browser in a structured manner . Go模板语言的一大优点是自动转义数据. No need to worry about XSS attacks because Go parses HTML templates and escapes all input before displaying it to the browser.

5.2 The first template

Writing templates in Go is very easy. This example shows a TODO list, written as an unordered list (ul) in HTML. ** When rendering a template, the data passed in can be any type of data structure in Go. **It can be a simple string or number, or even a nested data structure, as shown in the example below. To access data in the template, the topmost variable is { {.}}accessed via . The dot inside the curly braces is called the root element for pipes and data.

data := TodoPageData{
    
    
    PageTitle: "My TODO list",
    Todos: []Todo{
    
    
        {
    
    Title: "Task 1", Done: false},
        {
    
    Title: "Task 2", Done: true},
        {
    
    Title: "Task 3", Done: true},
    },
}
<h1>{
    
    {
    
    .PageTitle}}</h1>
<ul>
    {
    
    {
    
    range .Todos}}
        {
    
    {
    
    if .Done}}
            <li class="done">{
    
    {
    
    .Title}}</li>
        {
    
    {
    
    else}}
            <li>{
    
    {
    
    .Title}}</li>
        {
    
    {
    
    end}}
    {
    
    {
    
    end}}
</ul>

5.3 Control structure

The templating language contains a rich set of control structures for rendering HTML. Here you will get an overview of the most commonly used ones. For a detailed list of all possible structures, visit: text/template

Control Structure Definition
{ {/* a comment */}} Defines a comment
{ {.}} Renders the root element
{ {.Title}} Renders the “Title”-field in a nested element
{ {if .Done}} { {else}} { {end}} Defines an if-Statement
{ {range .All}} { {.}} { {end}} Loops over all “Todos” and renders
{ {block “content” .}} { {end}} Defines a block with the name “content”

5.4 Parsing templates from files

Templates can parse both strings and files on disk. Typically, templates are backups from disk, and this example shows how to do that. In this example, there is a template file named layout.html.

tmpl, err := template.ParseFiles("layout.html")
// or
tmpl := template.Must(template.ParseFiles("layout.html"))

5.5 Executing templates in request handlers

After the template has been parsed from disk, it is ready to be used in the request handler. ExecuteThe function takes one io.Writerfor writing out the template and one interface{}for passing data into the template. when http.ResponseWritercalling the function. Content-Typeheader is automatically set in the HTTP response toContent-Type: text/html; charset=utf-8

func(w http.ResponseWriter, r *http.Request) {
    
    
    tmpl.Execute(w, "data goes here")
}

The Code

package main


import (
    "html/template"
    "net/http"
)



type Todo struct {
    
    
    Title string
    Done  bool
}



type TodoPageData struct {
    
    
    PageTitle string
    Todos     []Todo
}



func main() {
    
    
    tmpl := template.Must(template.ParseFiles("layout.html"))
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    
    
        data := TodoPageData{
    
    
            PageTitle: "My TODO list",
            Todos: []Todo{
    
    
                {
    
    Title: "Task 1", Done: false},
                {
    
    Title: "Task 2", Done: true},
                {
    
    Title: "Task 3", Done: true},
            },
        }
        tmpl.Execute(w, data)
    })
    http.ListenAndServe(":80", nil)
}

layout.htmlas follows:

<h1>{
   
   {.PageTitle}}</h1>
<ul>
    {
   
   {range .Todos}}
    {
   
   {if .Done}}
    <li class="done">{
   
   {.Title}}</li>
    {
   
   {else}}
    <li>{
   
   {.Title}}</li>
    {
   
   {end}}
    {
   
   {end}}
</ul><h1>{
   
   {.PageTitle}}</h1>
<ul>
    {
   
   {range .Todos}}
    {
   
   {if .Done}}
    <li class="done">{
   
   {.Title}}</li>
    {
   
   {else}}
    <li>{
   
   {.Title}}</li>
    {
   
   {end}}
    {
   
   {end}}
</ul>

6、Assets and Files

This example will show how to serve static files such as CSS, JavaScript or images from a specific directory.

// static-files.go
package main

import "net/http"

func main() {
    
    
    fs := http.FileServer(http.Dir("assets/"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))

    http.ListenAndServe(":8080", nil)
}
$ tree assets/
assets/
└── css
    └── styles.css
$ go run static-files.go

$ curl -s http://localhost:8080/static/css/styles.css
body {
    
    
    background-color: black;
}

7. Form

This example will show how to simulate a contact form and parse the message into a struct.

// forms.go
package main

import (
    "html/template"
    "net/http"
)

type ContactDetails struct {
    
    
    Email   string
    Subject string
    Message string
}

func main() {
    
    
    tmpl := template.Must(template.ParseFiles("forms.html"))

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    
    
        if r.Method != http.MethodPost {
    
    
            tmpl.Execute(w, nil)
            return
        }

        details := ContactDetails{
    
    
            Email:   r.FormValue("email"),
            Subject: r.FormValue("subject"),
            Message: r.FormValue("message"),
        }

        // do something with details
        _ = details

        tmpl.Execute(w, struct{
    
     Success bool }{
    
    true})
    })

    http.ListenAndServe(":8080", nil)
}
<!-- forms.html -->
{
   
   {if .Success}}
    <h1>Thanks for your message!</h1>
{
   
   {else}}
    <h1>Contact</h1>
    <form method="POST">
        <label>Email:</label><br />
        <input type="text" name="email"><br />
        <label>Subject:</label><br />
        <input type="text" name="subject"><br />
        <label>Message:</label><br />
        <textarea name="message"></textarea><br />
        <input type="submit">
    </form>
{
   
   {end}}

Guess you like

Origin blog.csdn.net/chinusyan/article/details/130091165