├── Chapter01
├── goproject.conf
├── gulpfile.js
├── romanNumerals
│ └── data.go
└── romanserver
│ ├── data
│ └── roman.go
│ └── main.go
├── Chapter02
├── basicHandler.go
├── customMux.go
├── execService.go
├── fileserver.go
├── greek.txt
├── latin.txt
├── multipleHandlers.go
├── muxRouter.go
└── queryParameters.go
├── Chapter03
├── RPCClient.go
├── RPCServer.go
├── books.json
├── cityAPI.go
├── closure.go
├── customMiddleware.go
├── jsonRPCServer.go
├── loggingMiddleware.go
├── multipleMiddleware.go
└── multipleMiddlewareWithAlice.go
├── Chapter04
├── basicExample.go
├── dbutils
│ ├── init-tables.go
│ └── models.go
├── ginExamples
│ └── ginBasic.go
├── railAPI
│ └── main.go
├── railAPIGin
│ └── main.go
├── railAPIRevel
│ ├── README.md
│ ├── app
│ │ ├── controllers
│ │ │ └── app.go
│ │ ├── init.go
│ │ └── views
│ │ │ ├── App
│ │ │ └── Index.html
│ │ │ ├── debug.html
│ │ │ ├── errors
│ │ │ ├── 404.html
│ │ │ └── 500.html
│ │ │ ├── flash.html
│ │ │ ├── footer.html
│ │ │ └── header.html
│ ├── conf
│ │ ├── app.conf
│ │ └── routes
│ ├── messages
│ │ └── sample.en
│ ├── public
│ │ ├── css
│ │ │ └── bootstrap-3.3.6.min.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ ├── glyphicons-halflings-regular.woff
│ │ │ └── glyphicons-halflings-regular.woff2
│ │ ├── img
│ │ │ └── favicon.png
│ │ └── js
│ │ │ ├── bootstrap-3.3.6.min.js
│ │ │ └── jquery-2.2.4.min.js
│ └── tests
│ │ └── apptest.go
└── sqliteFundamentals.go
├── Chapter05
├── category.json
├── mgoIntro.go
├── movieAPI.go
├── movieAPI_updated.go
├── order.json
├── product.json
├── review.json
└── user.json
├── Chapter06
├── grpc_example
│ ├── datafiles
│ │ ├── transaction.pb.go
│ │ └── transaction.proto
│ ├── grpcClient
│ │ └── client.go
│ └── grpcServer
│ │ └── server.go
├── protobufs
│ ├── main.go
│ └── main_json.go
├── protofiles
│ ├── person.pb.go
│ └── person.proto
└── serverPush
│ ├── datafiles
│ ├── transaction.pb.go
│ └── transaction.proto
│ ├── grpcClient
│ └── client.go
│ └── grpcServer
│ └── server.go
├── Chapter07
├── base62example
│ ├── base62
│ │ └── encodeutils.go
│ └── usebase62.go
├── basicexample
│ ├── main.go
│ └── models
│ │ └── models.go
├── jsonstore
│ ├── main.go
│ └── models
│ │ └── models.go
└── urlshortener
│ ├── main.go
│ ├── main_test.go
│ ├── models
│ └── models.go
│ └── utils
│ └── encodeutils.go
├── Chapter08
├── cli
│ ├── cliBasic.go
│ └── storeMarks.go
├── flagExample.go
├── flagExampleMultiParam.go
├── githubAPI
│ ├── getRepos.go
│ ├── gitTool.go
│ ├── sample1.txt
│ └── sample2.txt
├── grequests
│ ├── basicRequest.go
│ └── jsonRequest.go
└── initFlag.go
├── Chapter09
├── encryptService
│ ├── helpers
│ │ ├── endpoints.go
│ │ ├── implementations.go
│ │ ├── jsonutils.go
│ │ └── models.go
│ └── main.go
├── encryptServiceWithInstrumentation
│ ├── helpers
│ │ ├── endpoints.go
│ │ ├── implementations.go
│ │ ├── instrumentation.go
│ │ ├── jsonutils.go
│ │ ├── middleware.go
│ │ └── models.go
│ └── main.go
├── encryptServiceWithLogging
│ ├── helpers
│ │ ├── endpoints.go
│ │ ├── implementations.go
│ │ ├── jsonutils.go
│ │ ├── middleware.go
│ │ └── models.go
│ └── main.go
└── encryptString
│ ├── main.go
│ └── utils
│ └── utils.go
├── Chapter10
├── basicServer
│ ├── app.log
│ └── main.go
├── myproject.conf
└── securenginx.conf
├── Chapter11
├── Dockerfile
├── Kong.postman_collection.json
├── kongExample
│ └── main.go
└── kong_install_docker.txt
├── Chapter12
├── jwtAuth
│ └── main.go
├── simpleAuth
│ └── main.go
└── simpleAuthWithRedis
│ └── main.go
├── LICENSE
└── README.md
/Chapter01/goproject.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | logfile = /tmp/supervisord.log
3 |
4 | [program:myserver]
5 | command=/home/floyed/golab/bin/myserver
6 | autostart=true
7 | autorestart=true
8 | redirect_stderr=true
9 |
--------------------------------------------------------------------------------
/Chapter01/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require("gulp");
2 | var shell = require('gulp-shell');
3 |
4 | // This compiles new binary with source change
5 | gulp.task("install-binary", shell.task([
6 | 'go install github.com/narenaryan/romanserver'
7 | ]));
8 |
9 | // Second argument tells install-binary is a deapendency for restart-supervisor
10 | gulp.task("restart-supervisor", ["install-binary"], shell.task([
11 | 'supervisorctl restart myserver'
12 | ]))
13 |
14 | gulp.task('watch', function() {
15 | // Watch the source code for all changes
16 | gulp.watch("*", ['install-binary', 'restart-supervisor']);
17 |
18 | });
19 |
20 | gulp.task('default', ['watch']);
21 |
--------------------------------------------------------------------------------
/Chapter01/romanNumerals/data.go:
--------------------------------------------------------------------------------
1 | package romanNumerals
2 |
3 | var Numerals = map[int]string{
4 | 10: "X",
5 | 9: "IX",
6 | 8: "VIII",
7 | 7: "VII",
8 | 6: "VI",
9 | 5: "V",
10 | 4: "IV",
11 | 3: "III",
12 | 2: "II",
13 | 1: "I",
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter01/romanserver/data/roman.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | const romanNumerals = map[string]string{
4 | 10: "X",
5 | 9: "IX",
6 | 8: "VIII",
7 | 7: "VII",
8 | 6: "VI",
9 | 5: "V",
10 | 4: "IV",
11 | 3: "III",
12 | 2: "II",
13 | 1: "I",
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter01/romanserver/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/narenaryan/romanNumerals"
6 | "html"
7 | "net/http"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | func main() {
14 | // http package has methods for dealing with requests
15 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
16 | urlPathElements := strings.Split(r.URL.Path, "/")
17 | // If request is GET with correct syntax
18 | if urlPathElements[1] == "roman_number" {
19 | number, _ := strconv.Atoi(strings.TrimSpace(urlPathElements[2]))
20 | if number == 0 || number > 10 {
21 | w.WriteHeader(http.StatusNotFound)
22 | w.Write([]byte("404 - Not Found"))
23 | } else {
24 | fmt.Fprintf(w, "%q", html.EscapeString(romanNumerals.Numerals[number]))
25 | }
26 | } else {
27 | // For all other requests, tell that Client sent a bad request
28 | w.WriteHeader(http.StatusBadRequest)
29 | w.Write([]byte("400 - Bad request"))
30 | }
31 | })
32 | // Create a server and run it on 8000 port
33 | s := &http.Server{
34 | Addr: ":8000",
35 | ReadTimeout: 10 * time.Second,
36 | WriteTimeout: 10 * time.Second,
37 | MaxHeaderBytes: 1 << 20,
38 | }
39 | s.ListenAndServe()
40 | }
41 |
--------------------------------------------------------------------------------
/Chapter02/basicHandler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "log"
7 | )
8 |
9 | // hello world, the web server
10 | func MyServer(w http.ResponseWriter, req *http.Request) {
11 | io.WriteString(w, "hello, world!\n")
12 | }
13 |
14 | func main() {
15 | http.HandleFunc("/hello", MyServer)
16 | log.Fatal(http.ListenAndServe(":8000", nil))
17 | }
18 |
--------------------------------------------------------------------------------
/Chapter02/customMux.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "net/http"
7 | )
8 |
9 | type CustomServeMux struct {
10 | }
11 |
12 | func (p *CustomServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
13 | if r.URL.Path == "/" {
14 | giveRandom(w, r)
15 | return
16 | }
17 | http.NotFound(w, r)
18 | return
19 | }
20 |
21 | func giveRandom(w http.ResponseWriter, r *http.Request) {
22 | fmt.Fprintf(w, "Your random number is: %f", rand.Float64())
23 | }
24 |
25 | func main() {
26 | mux := &CustomServeMux{}
27 | http.ListenAndServe(":8000", mux)
28 | }
29 |
--------------------------------------------------------------------------------
/Chapter02/execService.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/julienschmidt/httprouter"
7 | "log"
8 | "net/http"
9 | "os/exec"
10 | )
11 |
12 |
13 | func getCommandOutput(command string, arguments ...string) string{
14 | // args... unpacks arguments array into elements
15 | cmd := exec.Command(command, arguments...)
16 | var out bytes.Buffer
17 | var stderr bytes.Buffer
18 | cmd.Stdout = &out
19 | cmd.Stderr = &stderr
20 | err := cmd.Start()
21 | if err != nil {
22 | log.Fatal(fmt.Sprint(err) + ": " + stderr.String())
23 | }
24 | err = cmd.Wait()
25 | if err != nil {
26 | log.Fatal(fmt.Sprint(err) + ": " + stderr.String())
27 | }
28 | return out.String()
29 | }
30 |
31 | func goVersion(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
32 | fmt.Fprintf(w, getCommandOutput("/usr/local/bin/go", "version"))
33 | }
34 |
35 | func getFileContent(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
36 | fmt.Fprintf(w, getCommandOutput("/bin/cat", params.ByName("name")))
37 | }
38 |
39 | func main() {
40 | router := httprouter.New()
41 | // Mapping to methods is possible with HttpRouter
42 | router.GET("/api/v1/go-version", goVersion)
43 | // Path variable called name used here
44 | router.GET("/api/v1/show-file/:name", getFileContent)
45 | log.Fatal(http.ListenAndServe(":8000", router))
46 | }
47 |
--------------------------------------------------------------------------------
/Chapter02/fileserver.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/julienschmidt/httprouter"
5 | "log"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | router := httprouter.New()
11 | // Mapping to methods is possible with HttpRouter
12 | router.ServeFiles("/static/*filepath", http.Dir("/Users/naren/static"))
13 | log.Fatal(http.ListenAndServe(":8000", router))
14 | }
15 |
--------------------------------------------------------------------------------
/Chapter02/greek.txt:
--------------------------------------------------------------------------------
1 | Οἱ δὲ Φοίνιϰες οὗτοι οἱ σὺν Κάδμῳ ἀπιϰόμενοι.. ἐσήγαγον διδασϰάλια ἐς τοὺς ῞Ελληνας ϰαὶ δὴ ϰαὶ γράμματα, οὐϰ ἐόντα πρὶν ῞Ελλησι ὡς ἐμοὶ δοϰέειν, πρῶτα μὲν τοῖσι ϰαὶ ἅπαντες χρέωνται Φοίνιϰες· μετὰ δὲ χρόνου προβαίνοντος ἅμα τῇ ϕωνῇ μετέβαλον ϰαὶ τὸν ϱυϑμὸν τῶν γραμμάτων. Περιοίϰεον δέ σϕεας τὰ πολλὰ τῶν χώρων τοῦτον τὸν χρόνον ῾Ελλήνων ῎Ιωνες· οἳ παραλαβόντες διδαχῇ παρὰ τῶν Φοινίϰων τὰ γράμματα, μεταρρυϑμίσαντές σϕεων ὀλίγα ἐχρέωντο, χρεώμενοι δὲ ἐϕάτισαν, ὥσπερ ϰαὶ τὸ δίϰαιον ἔϕερε ἐσαγαγόντων Φοινίϰων ἐς τὴν ῾Ελλάδα, ϕοινιϰήια ϰεϰλῆσϑαι.
--------------------------------------------------------------------------------
/Chapter02/latin.txt:
--------------------------------------------------------------------------------
1 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu.
--------------------------------------------------------------------------------
/Chapter02/multipleHandlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | newMux := http.NewServeMux()
11 |
12 | newMux.HandleFunc("/randomFloat", func(w http.ResponseWriter, r *http.Request) {
13 | fmt.Fprintln(w, rand.Float64())
14 | })
15 |
16 | newMux.HandleFunc("/randomInt", func(w http.ResponseWriter, r *http.Request) {
17 | fmt.Fprintln(w, rand.Intn(100))
18 | })
19 | http.ListenAndServe(":8000", newMux)
20 | }
21 |
--------------------------------------------------------------------------------
/Chapter02/muxRouter.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gorilla/mux"
6 | "log"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | func ArticleHandler(w http.ResponseWriter, r *http.Request) {
12 | vars := mux.Vars(r)
13 | w.WriteHeader(http.StatusOK)
14 | fmt.Fprintf(w, "Category is: %v\n", vars["category"])
15 | fmt.Fprintf(w, "ID is: %v\n", vars["id"])
16 | }
17 | func main() {
18 | // Create a new router
19 | r := mux.NewRouter()
20 | // Attach an elegant path with handler
21 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
22 | srv := &http.Server{
23 | Handler: r,
24 | Addr: "127.0.0.1:8000",
25 | // Good practice: enforce timeouts for servers you create!
26 | WriteTimeout: 15 * time.Second,
27 | ReadTimeout: 15 * time.Second,
28 | }
29 | log.Fatal(srv.ListenAndServe())
30 | }
31 |
--------------------------------------------------------------------------------
/Chapter02/queryParameters.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gorilla/mux"
6 | "log"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | func QueryHandler(w http.ResponseWriter, r *http.Request) {
12 | queryParams := r.URL.Query()
13 | w.WriteHeader(http.StatusOK)
14 | fmt.Fprintf(w, "Got parameter id:%s!\n", queryParams["id"][0])
15 | fmt.Fprintf(w, "Got parameter category:%s!", queryParams["category"][0])
16 | }
17 |
18 | func main() {
19 | // Create a new router
20 | r := mux.NewRouter()
21 | // Attach an elegant path with handler
22 | r.HandleFunc("/articles", QueryHandler)
23 | r.Queries("id", "category")
24 | srv := &http.Server{
25 | Handler: r,
26 | Addr: "127.0.0.1:8000",
27 | // Good practice: enforce timeouts for servers you create!
28 | WriteTimeout: 15 * time.Second,
29 | ReadTimeout: 15 * time.Second,
30 | }
31 | log.Fatal(srv.ListenAndServe())
32 | }
33 |
--------------------------------------------------------------------------------
/Chapter03/RPCClient.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net/rpc"
6 | )
7 |
8 | type Args struct {
9 | }
10 |
11 | func main() {
12 | var reply int64
13 | args := Args{}
14 | client, err := rpc.DialHTTP("tcp", "localhost"+":1234")
15 | if err != nil {
16 | log.Fatal("dialing:", err)
17 | }
18 | err = client.Call("TimeServer.GiveServerTime", args, &reply)
19 | if err != nil {
20 | log.Fatal("arith error:", err)
21 | }
22 | log.Printf("%d", reply)
23 | }
24 |
--------------------------------------------------------------------------------
/Chapter03/RPCServer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "net"
6 | "net/rpc"
7 | "time"
8 | "net/http"
9 | )
10 |
11 | type Args struct{}
12 |
13 | type TimeServer int64
14 |
15 | func (t *TimeServer) GiveServerTime(args *Args, reply *int64) error {
16 | *reply = time.Now().Unix()
17 | return nil
18 | }
19 |
20 | func main() {
21 | timeserver := new(TimeServer)
22 | rpc.Register(timeserver)
23 | rpc.HandleHTTP()
24 | l, e := net.Listen("tcp", ":1234")
25 | if e != nil {
26 | log.Fatal("listen error:", e)
27 | }
28 | http.Serve(l, nil)
29 | }
30 |
--------------------------------------------------------------------------------
/Chapter03/books.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "1234",
4 | "name": "In the sunburned country",
5 | "author": "Bill Bryson"
6 | },
7 | {
8 | "id":"2345",
9 | "name": "The picture of Dorian Gray",
10 | "author": "Oscar Wilde"
11 | }
12 | ]
--------------------------------------------------------------------------------
/Chapter03/cityAPI.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | )
8 |
9 | type city struct {
10 | Name string
11 | Area uint64
12 | }
13 |
14 | func mainLogic(w http.ResponseWriter, r *http.Request) {
15 | // Check if method is POST
16 | if r.Method == "POST" {
17 | var tempCity city
18 | decoder := json.NewDecoder(r.Body)
19 | err := decoder.Decode(&tempCity)
20 | if err != nil {
21 | panic(err)
22 | }
23 | defer r.Body.Close()
24 | // Your resource creation logic goes here. For now it is plain print to console
25 | fmt.Printf("Got %s city with area of %d sq miles!\n", tempCity.Name, tempCity.Area)
26 | // Tell everything is fine
27 | w.WriteHeader(http.StatusOK)
28 | w.Write([]byte("201 - Created"))
29 | } else {
30 | // Say method not allowed
31 | w.WriteHeader(http.StatusMethodNotAllowed)
32 | w.Write([]byte("405 - Method Not Allowed"))
33 | }
34 | }
35 |
36 | func main() {
37 | http.HandleFunc("/city", mainLogic)
38 | http.ListenAndServe(":8000", nil)
39 | }
40 |
--------------------------------------------------------------------------------
/Chapter03/closure.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | func main() {
8 | numGenerator := generator();
9 | for i := 0; i < 5; i++ {
10 | fmt.Print(numGenerator(), "\t");
11 | }
12 | }
13 |
14 | func generator() func() int{
15 | var i = 0;
16 | return func() int{
17 | i += 1;
18 | return i;
19 | }
20 |
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/Chapter03/customMiddleware.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | func middleware(handler http.Handler) http.Handler {
9 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
10 | fmt.Println("Executing middleware before request phase!")
11 | // Pass control back to the handler
12 | handler.ServeHTTP(w, r)
13 | fmt.Println("Executing middleware after response phase!")
14 | })
15 | }
16 |
17 | func mainLogic(w http.ResponseWriter, r *http.Request) {
18 | // Business logic goes here
19 | fmt.Println("Executing mainHandler...")
20 | w.Write([]byte("OK"))
21 | }
22 |
23 | func main() {
24 | // HandlerFunc returns a HTTP Handler
25 | mainLogicHandler := http.HandlerFunc(mainLogic)
26 | http.Handle("/", middleware(mainLogicHandler))
27 | http.ListenAndServe(":8000", nil)
28 | }
29 |
--------------------------------------------------------------------------------
/Chapter03/jsonRPCServer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | jsonparse "encoding/json"
5 | "github.com/gorilla/mux"
6 | "github.com/gorilla/rpc"
7 | "github.com/gorilla/rpc/json"
8 | "io/ioutil"
9 | "log"
10 | "net/http"
11 | "os"
12 | )
13 |
14 | type Args struct {
15 | Id string
16 | }
17 |
18 | type Book struct {
19 | Id string `"json:string,omitempty"`
20 | Name string `"json:name,omitempty"`
21 | Author string `"json:author,omitempty"`
22 | }
23 |
24 | type JSONServer struct{}
25 |
26 | func (t *JSONServer) GiveBookDetail(r *http.Request, args *Args, reply *Book) error {
27 | var books []Book
28 | raw, readerr := ioutil.ReadFile("./books.json")
29 | if readerr != nil {
30 | log.Println("error:", readerr)
31 | os.Exit(1)
32 | }
33 | marshalerr := jsonparse.Unmarshal(raw, &books)
34 | if marshalerr != nil {
35 | log.Println("error:", marshalerr)
36 | os.Exit(1)
37 | }
38 | // Iterate over JSON data to find the given book
39 | for _, book := range books {
40 | if book.Id == args.Id {
41 | *reply = book
42 | break
43 | }
44 | }
45 | return nil
46 | }
47 |
48 | func main() {
49 | s := rpc.NewServer()
50 | s.RegisterCodec(json.NewCodec(), "application/json")
51 | s.RegisterService(new(JSONServer), "")
52 | r := mux.NewRouter()
53 | r.Handle("/rpc", s)
54 | http.ListenAndServe(":1234", r)
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/Chapter03/loggingMiddleware.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gorilla/handlers"
5 | "github.com/gorilla/mux"
6 | "log"
7 | "os"
8 | "net/http"
9 | )
10 |
11 | func mainLogic(w http.ResponseWriter, r *http.Request) {
12 | log.Println("Processing request!")
13 | w.Write([]byte("OK"))
14 | log.Println("Finished processing request")
15 | }
16 |
17 | func main() {
18 | r := mux.NewRouter()
19 | r.HandleFunc("/", mainLogic)
20 | loggedRouter := handlers.LoggingHandler(os.Stdout, r)
21 | http.ListenAndServe(":8000", loggedRouter)
22 | }
23 |
--------------------------------------------------------------------------------
/Chapter03/multipleMiddleware.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "log"
6 | "net/http"
7 | "strconv"
8 | "time"
9 | )
10 |
11 | type city struct {
12 | Name string
13 | Area uint64
14 | }
15 |
16 | // Middleware to check content type as JSON
17 | func filterContentType(handler http.Handler) http.Handler {
18 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19 | log.Println("Currently in the check content type middleware")
20 | // Filtering requests by MIME type
21 | if r.Header.Get("Content-type") != "application/json" {
22 | w.WriteHeader(http.StatusUnsupportedMediaType)
23 | w.Write([]byte("415 - Unsupported Media Type. Please send JSON"))
24 | return
25 | }
26 | handler.ServeHTTP(w, r)
27 | })
28 | }
29 |
30 | // Middleware to add server timestamp for response cookie
31 | func setServerTimeCookie(handler http.Handler) http.Handler {
32 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33 | handler.ServeHTTP(w, r)
34 | // Setting cookie to each and every response
35 | cookie := http.Cookie{Name: "Server-Time(UTC)", Value: strconv.FormatInt(time.Now().Unix(), 10)}
36 | http.SetCookie(w, &cookie)
37 | log.Println("Currently in the set server time middleware")
38 | })
39 | }
40 |
41 | func mainLogic(w http.ResponseWriter, r *http.Request) {
42 | // Check if method is POST
43 | if r.Method == "POST" {
44 | var tempCity city
45 | decoder := json.NewDecoder(r.Body)
46 | err := decoder.Decode(&tempCity)
47 | if err != nil {
48 | panic(err)
49 | }
50 | defer r.Body.Close()
51 | // Your resource creation logic goes here. For now it is plain print to console
52 | log.Printf("Got %s city with area of %d sq miles!\n", tempCity.Name, tempCity.Area)
53 | // Tell everything is fine
54 | w.WriteHeader(http.StatusOK)
55 | w.Write([]byte("201 - Created"))
56 | } else {
57 | // Say method not allowed
58 | w.WriteHeader(http.StatusMethodNotAllowed)
59 | w.Write([]byte("405 - Method Not Allowed"))
60 | }
61 | }
62 |
63 | func main() {
64 | mainLogicHandler := http.HandlerFunc(mainLogic)
65 | http.Handle("/city", filterContentType(setServerTimeCookie(mainLogicHandler)))
66 | http.ListenAndServe(":8000", nil)
67 | }
68 |
--------------------------------------------------------------------------------
/Chapter03/multipleMiddlewareWithAlice.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/justinas/alice"
6 | "log"
7 | "net/http"
8 | "strconv"
9 | "time"
10 | )
11 |
12 | type city struct {
13 | Name string
14 | Area uint64
15 | }
16 |
17 | // Middleware to check content type as JSON
18 | func filterContentType(handler http.Handler) http.Handler {
19 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20 | log.Println("Currently in the check content type middleware")
21 | // Filtering requests by MIME type
22 | if r.Header.Get("Content-type") != "application/json" {
23 | w.WriteHeader(http.StatusUnsupportedMediaType)
24 | w.Write([]byte("415 - Unsupported Media Type. Please send JSON"))
25 | return
26 | }
27 | handler.ServeHTTP(w, r)
28 | })
29 | }
30 |
31 | // Middleware to add server timestamp for response cookie
32 | func setServerTimeCookie(handler http.Handler) http.Handler {
33 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
34 | handler.ServeHTTP(w, r)
35 | // Setting cookie to each and every response
36 | cookie := http.Cookie{Name: "Server-Time(UTC)", Value: strconv.FormatInt(time.Now().Unix(), 10)}
37 | http.SetCookie(w, &cookie)
38 | log.Println("Currently in the set server time middleware")
39 | })
40 | }
41 |
42 | func mainLogic(w http.ResponseWriter, r *http.Request) {
43 | // Check if method is POST
44 | if r.Method == "POST" {
45 | var tempCity city
46 | decoder := json.NewDecoder(r.Body)
47 | err := decoder.Decode(&tempCity)
48 | if err != nil {
49 | panic(err)
50 | }
51 | defer r.Body.Close()
52 | // Your resource creation logic goes here. For now it is plain print to console
53 | log.Printf("Got %s city with area of %d sq miles!\n", tempCity.Name, tempCity.Area)
54 | // Tell everything is fine
55 | w.WriteHeader(http.StatusOK)
56 | w.Write([]byte("201 - Created"))
57 | } else {
58 | // Say method not allowed
59 | w.WriteHeader(http.StatusMethodNotAllowed)
60 | w.Write([]byte("405 - Method Not Allowed"))
61 | }
62 | }
63 |
64 | func main() {
65 | mainLogicHandler := http.HandlerFunc(mainLogic)
66 | chain := alice.New(filterContentType, setServerTimeCookie).Then(mainLogicHandler)
67 | http.Handle("/city", chain)
68 | http.ListenAndServe(":8000", nil)
69 | }
70 |
--------------------------------------------------------------------------------
/Chapter04/basicExample.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/emicklei/go-restful"
6 | "io"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | func main() {
12 | webservice := new(restful.WebService)
13 | webservice.Route(webservice.GET("/ping").To(pingTime))
14 | restful.Add(webservice)
15 | http.ListenAndServe(":8000", nil)
16 | }
17 |
18 | func pingTime(req *restful.Request, resp *restful.Response) {
19 | io.WriteString(resp, fmt.Sprintf("%s", time.Now()))
20 | }
21 |
--------------------------------------------------------------------------------
/Chapter04/dbutils/init-tables.go:
--------------------------------------------------------------------------------
1 | package dbutils
2 |
3 | import "log"
4 | import "database/sql"
5 |
6 | func Initialize(dbDriver *sql.DB) {
7 | statement, driverError := dbDriver.Prepare(train)
8 | if driverError != nil {
9 | log.Println(driverError)
10 | }
11 | // Create train table
12 | _, statementError := statement.Exec()
13 | if statementError != nil {
14 | log.Println("Table already exists!")
15 | }
16 | statement, _ = dbDriver.Prepare(station)
17 | statement.Exec()
18 | statement, _ = dbDriver.Prepare(schedule)
19 | statement.Exec()
20 | log.Println("All tables created/initialized successfully!")
21 | }
22 |
--------------------------------------------------------------------------------
/Chapter04/dbutils/models.go:
--------------------------------------------------------------------------------
1 | package dbutils
2 |
3 | const train = `
4 | CREATE TABLE IF NOT EXISTS train (
5 | ID INTEGER PRIMARY KEY AUTOINCREMENT,
6 | DRIVER_NAME VARCHAR(64) NULL,
7 | OPERATING_STATUS BOOLEAN
8 | )
9 | `
10 |
11 | const station = `
12 | CREATE TABLE IF NOT EXISTS station (
13 | ID INTEGER PRIMARY KEY AUTOINCREMENT,
14 | NAME VARCHAR(64) NULL,
15 | OPENING_TIME TIME NULL,
16 | CLOSING_TIME TIME NULL
17 | )
18 | `
19 | const schedule = `
20 | CREATE TABLE IF NOT EXISTS schedule (
21 | ID INTEGER PRIMARY KEY AUTOINCREMENT,
22 | TRAIN_ID INT,
23 | STATION_ID INT,
24 | ARRIVAL_TIME TIME,
25 | FOREIGN KEY (TRAIN_ID) REFERENCES train(ID),
26 | FOREIGN KEY (STATION_ID) REFERENCES station(ID)
27 | )
28 | `
29 |
--------------------------------------------------------------------------------
/Chapter04/ginExamples/ginBasic.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | func main() {
10 | r := gin.Default()
11 | r.GET("/pingTime", func(c *gin.Context) {
12 | c.JSON(200, gin.H{
13 | "serverTime": time.Now().UTC(),
14 | })
15 | })
16 |
17 | r.Run(":8000") // Default listen and serve on 0.0.0.0:8080
18 | }
19 |
--------------------------------------------------------------------------------
/Chapter04/railAPI/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "encoding/json"
6 | "log"
7 | "net/http"
8 | "time"
9 |
10 | "github.com/emicklei/go-restful"
11 | _ "github.com/mattn/go-sqlite3"
12 | "github.com/narenaryan/dbutils"
13 | )
14 |
15 | // DB Driver visible to whole program
16 | var DB *sql.DB
17 |
18 | // TrainResource is the model for holding rail information
19 | type TrainResource struct {
20 | ID int
21 | DriverName string
22 | OperatingStatus bool
23 | }
24 |
25 | // StationResource holds information about locations
26 | type StationResource struct {
27 | ID int
28 | Name string
29 | OpeningTime time.Time
30 | ClosingTime time.Time
31 | }
32 |
33 | // ScheduleResource links both trains and stations
34 | type ScheduleResource struct {
35 | ID int
36 | TrainID int
37 | StationID int
38 | ArrivalTime time.Time
39 | }
40 |
41 | // Register adds paths and routes to container
42 | func (t *TrainResource) Register(container *restful.Container) {
43 | ws := new(restful.WebService)
44 | ws.
45 | Path("/v1/trains").
46 | Consumes(restful.MIME_JSON).
47 | Produces(restful.MIME_JSON) // you can specify this per route as well
48 |
49 | ws.Route(ws.GET("/{train-id}").To(t.getTrain))
50 | ws.Route(ws.POST("").To(t.createTrain))
51 | ws.Route(ws.DELETE("/{train-id}").To(t.removeTrain))
52 |
53 | container.Add(ws)
54 | }
55 |
56 | // GET http://localhost:8000/v1/trains/1
57 | func (t TrainResource) getTrain(request *restful.Request, response *restful.Response) {
58 | id := request.PathParameter("train-id")
59 | err := DB.QueryRow("select ID, DRIVER_NAME, OPERATING_STATUS FROM train where id=?", id).Scan(&t.ID, &t.DriverName, &t.OperatingStatus)
60 | if err != nil {
61 | log.Println(err)
62 | response.AddHeader("Content-Type", "text/plain")
63 | response.WriteErrorString(http.StatusNotFound, "Train could not be found.")
64 | } else {
65 | response.WriteEntity(t)
66 | }
67 | }
68 |
69 | // POST http://localhost:8000/v1/trains
70 | func (t TrainResource) createTrain(request *restful.Request, response *restful.Response) {
71 | log.Println(request.Request.Body)
72 | decoder := json.NewDecoder(request.Request.Body)
73 | var b TrainResource
74 | err := decoder.Decode(&b)
75 | log.Println(b.DriverName, b.OperatingStatus)
76 |
77 | // Error handling is obvious here. So omitting...
78 | statement, _ := DB.Prepare("insert into train (DRIVER_NAME, OPERATING_STATUS) values (?, ?)")
79 | result, err := statement.Exec(b.DriverName, b.OperatingStatus)
80 | if err == nil {
81 | newID, _ := result.LastInsertId()
82 | b.ID = int(newID)
83 | response.WriteHeaderAndEntity(http.StatusCreated, b)
84 | } else {
85 | response.AddHeader("Content-Type", "text/plain")
86 | response.WriteErrorString(http.StatusInternalServerError, err.Error())
87 | }
88 | }
89 |
90 | // DELETE http://localhost:8000/v1/trains/1
91 | func (t TrainResource) removeTrain(request *restful.Request, response *restful.Response) {
92 | id := request.PathParameter("train-id")
93 | statement, _ := DB.Prepare("delete from train where id=?")
94 | _, err := statement.Exec(id)
95 | if err == nil {
96 | response.WriteHeader(http.StatusOK)
97 | } else {
98 | response.AddHeader("Content-Type", "text/plain")
99 | response.WriteErrorString(http.StatusInternalServerError, err.Error())
100 | }
101 | }
102 |
103 | func main() {
104 | var err error
105 | DB, err = sql.Open("sqlite3", "./railapi.db")
106 | if err != nil {
107 | log.Println("Driver creation failed!")
108 | }
109 | dbutils.Initialize(DB)
110 | wsContainer := restful.NewContainer()
111 | wsContainer.Router(restful.CurlyRouter{})
112 | t := TrainResource{}
113 | t.Register(wsContainer)
114 |
115 | log.Printf("start listening on localhost:8000")
116 | server := &http.Server{Addr: ":8000", Handler: wsContainer}
117 | log.Fatal(server.ListenAndServe())
118 | }
119 |
--------------------------------------------------------------------------------
/Chapter04/railAPIGin/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "database/sql"
5 | "log"
6 | "net/http"
7 |
8 | "github.com/gin-gonic/gin"
9 | _ "github.com/mattn/go-sqlite3"
10 | "github.com/narenaryan/dbutils"
11 | )
12 |
13 | // DB Driver visible to whole program
14 | var DB *sql.DB
15 |
16 | // StationResource holds information about locations
17 | type StationResource struct {
18 | ID int `json:"id"`
19 | Name string `json:"name"`
20 | OpeningTime string `json:"opening_time"`
21 | ClosingTime string `json:"closing_time"`
22 | }
23 |
24 | // GetStation returns the station detail
25 | func GetStation(c *gin.Context) {
26 | var station StationResource
27 | id := c.Param("station_id")
28 | err := DB.QueryRow("select ID, NAME, CAST(OPENING_TIME as CHAR), CAST(CLOSING_TIME as CHAR) from station where id=?", id).Scan(&station.ID, &station.Name, &station.OpeningTime, &station.ClosingTime)
29 | if err != nil {
30 | log.Println(err)
31 | c.JSON(500, gin.H{
32 | "error": err.Error(),
33 | })
34 | } else {
35 | c.JSON(200, gin.H{
36 | "result": station,
37 | })
38 | }
39 | }
40 |
41 | // CreateStation handles the POST
42 | func CreateStation(c *gin.Context) {
43 | var station StationResource
44 | // Parse the body into our resrource
45 | if err := c.BindJSON(&station); err == nil {
46 | // Format Time to Go time format
47 | statement, _ := DB.Prepare("insert into station (NAME, OPENING_TIME, CLOSING_TIME) values (?, ?, ?)")
48 | result, _ := statement.Exec(station.Name, station.OpeningTime, station.ClosingTime)
49 | if err == nil {
50 | newID, _ := result.LastInsertId()
51 | station.ID = int(newID)
52 | c.JSON(http.StatusOK, gin.H{
53 | "result": station,
54 | })
55 | } else {
56 | c.String(http.StatusInternalServerError, err.Error())
57 | }
58 | } else {
59 | c.String(http.StatusInternalServerError, err.Error())
60 | }
61 | }
62 |
63 | // RemoveStation handles the removing of resource
64 | func RemoveStation(c *gin.Context) {
65 | id := c.Param("station-id")
66 | statement, _ := DB.Prepare("delete from station where id=?")
67 | _, err := statement.Exec(id)
68 | if err != nil {
69 | log.Println(err)
70 | c.JSON(500, gin.H{
71 | "error": err.Error(),
72 | })
73 | } else {
74 | c.String(http.StatusOK, "")
75 | }
76 | }
77 |
78 | func main() {
79 | var err error
80 | DB, err = sql.Open("sqlite3", "./railapi.db")
81 | if err != nil {
82 | log.Println("Driver creation failed!")
83 | }
84 | dbutils.Initialize(DB)
85 | r := gin.Default()
86 | // Add routes to REST verbs
87 | r.GET("/v1/stations/:station_id", GetStation)
88 | r.POST("/v1/stations", CreateStation)
89 | r.DELETE("/v1/stations/:station_id", RemoveStation)
90 |
91 | r.Run(":8000") // Default listen and serve on 0.0.0.0:8080
92 | }
93 |
--------------------------------------------------------------------------------
/Chapter04/railAPIRevel/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to Revel
2 |
3 | A high-productivity web framework for the [Go language](http://www.golang.org/).
4 |
5 |
6 | ### Start the web server:
7 |
8 | revel run myapp
9 |
10 | ### Go to http://localhost:9000/ and you'll see:
11 |
12 | "It works"
13 |
14 | ## Code Layout
15 |
16 | The directory structure of a generated Revel application:
17 |
18 | conf/ Configuration directory
19 | app.conf Main app configuration file
20 | routes Routes definition file
21 |
22 | app/ App sources
23 | init.go Interceptor registration
24 | controllers/ App controllers go here
25 | views/ Templates directory
26 |
27 | messages/ Message files
28 |
29 | public/ Public static assets
30 | css/ CSS files
31 | js/ Javascript files
32 | images/ Image files
33 |
34 | tests/ Test suites
35 |
36 |
37 | ## Help
38 |
39 | * The [Getting Started with Revel](http://revel.github.io/tutorial/gettingstarted.html).
40 | * The [Revel guides](http://revel.github.io/manual/index.html).
41 | * The [Revel sample apps](http://revel.github.io/examples/index.html).
42 | * The [API documentation](https://godoc.org/github.com/revel/revel).
43 |
44 |
--------------------------------------------------------------------------------
/Chapter04/railAPIRevel/app/controllers/app.go:
--------------------------------------------------------------------------------
1 | package controllers
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "strconv"
7 |
8 | "github.com/revel/revel"
9 | )
10 |
11 | type App struct {
12 | *revel.Controller
13 | }
14 |
15 | // TrainResource is the model for holding rail information
16 | type TrainResource struct {
17 | ID int `json:"id"`
18 | DriverName string `json:"driver_name"`
19 | OperatingStatus bool `json:"operating_status"`
20 | }
21 |
22 | // GetTrain handles GET on train resource
23 | func (c App) GetTrain() revel.Result {
24 | var train TrainResource
25 | // Getting the values from path parameters.
26 | id := c.Params.Route.Get("train-id")
27 | // use this ID to query from database and fill train table....
28 | train.ID, _ = strconv.Atoi(id)
29 | train.DriverName = "Logan" // Comes from DB
30 | train.OperatingStatus = true // Comes from DB
31 | c.Response.Status = http.StatusOK
32 | return c.RenderJSON(train)
33 | }
34 |
35 | // CreateTrain handles POST on train resource
36 | func (c App) CreateTrain() revel.Result {
37 | var train TrainResource
38 | c.Params.BindJSON(&train)
39 | // Use train.DriverName and train.OperatingStatus to insert into train table....
40 | train.ID = 2
41 | c.Response.Status = http.StatusCreated
42 | return c.RenderJSON(train)
43 | }
44 |
45 | // RemoveTrain implements DELETE on train resource
46 | func (c App) RemoveTrain() revel.Result {
47 | id := c.Params.Route.Get("train-id")
48 | // Use ID to delete record from train table....
49 | log.Println("Successfully deleted the resource:", id)
50 | c.Response.Status = http.StatusOK
51 | return c.RenderText("")
52 | }
53 |
--------------------------------------------------------------------------------
/Chapter04/railAPIRevel/app/init.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "github.com/revel/revel"
5 | )
6 |
7 | var (
8 | // AppVersion revel app version (ldflags)
9 | AppVersion string
10 |
11 | // BuildTime revel app build-time (ldflags)
12 | BuildTime string
13 | )
14 |
15 | func init() {
16 | // Filters is the default set of global filters.
17 | revel.Filters = []revel.Filter{
18 | revel.PanicFilter, // Recover from panics and display an error page instead.
19 | revel.RouterFilter, // Use the routing table to select the right Action
20 | revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
21 | revel.ParamsFilter, // Parse parameters into Controller.Params.
22 | revel.SessionFilter, // Restore and write the session cookie.
23 | revel.FlashFilter, // Restore and write the flash cookie.
24 | revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
25 | revel.I18nFilter, // Resolve the requested language
26 | HeaderFilter, // Add some security based headers
27 | revel.InterceptorFilter, // Run interceptors around the action.
28 | revel.CompressFilter, // Compress the result.
29 | revel.ActionInvoker, // Invoke the action.
30 | }
31 |
32 |
33 | // register startup functions with OnAppStart
34 | // revel.DevMode and revel.RunMode only work inside of OnAppStart. See Example Startup Script
35 | // ( order dependent )
36 | // revel.OnAppStart(ExampleStartupScript)
37 | // revel.OnAppStart(InitDB)
38 | // revel.OnAppStart(FillCache)
39 | }
40 |
41 | // HeaderFilter adds common security headers
42 | // TODO turn this into revel.HeaderFilter
43 | // should probably also have a filter for CSRF
44 | // not sure if it can go in the same filter or not
45 | var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
46 | c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
47 | c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
48 | c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
49 |
50 | fc[0](c, fc[1:]) // Execute the next filter stage.
51 | }
52 |
53 | //func ExampleStartupScript() {
54 | // // revel.DevMod and revel.RunMode work here
55 | // // Use this script to check for dev mode and set dev/prod startup scripts here!
56 | // if revel.DevMode == true {
57 | // // Dev mode
58 | // }
59 | //}
60 |
--------------------------------------------------------------------------------
/Chapter04/railAPIRevel/app/views/App/Index.html:
--------------------------------------------------------------------------------
1 | {{set . "title" "Home"}}
2 | {{template "header.html" .}}
3 |
4 | It works!
8 |
9 |
15 | {{.Description}} 16 |
17 | {{end}} 18 | {{end}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/errors/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |12 | This exception has been logged. 13 |
14 | {{end}} 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/flash.html: -------------------------------------------------------------------------------- 1 | {{if .flash.success}} 2 |