├── 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 |
5 |
6 |
7 |

It works!

8 |

9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 | {{template "flash.html" .}} 17 |
18 |
19 |
20 | 21 | {{template "footer.html" .}} 22 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/debug.html: -------------------------------------------------------------------------------- 1 | 20 | 44 | 45 | 46 | 65 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/errors/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Not found 5 | 6 | 7 | {{if eq .RunMode "dev"}} 8 | {{template "errors/404-dev.html" .}} 9 | {{else}} 10 | {{with .Error}} 11 |

12 | {{.Title}} 13 |

14 |

15 | {{.Description}} 16 |

17 | {{end}} 18 | {{end}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/errors/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Application error 5 | 6 | 7 | {{if eq .RunMode "dev"}} 8 | {{template "errors/500-dev.html" .}} 9 | {{else}} 10 |

Oops, an error occured

11 |

12 | This exception has been logged. 13 |

14 | {{end}} 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/flash.html: -------------------------------------------------------------------------------- 1 | {{if .flash.success}} 2 |
3 | {{.flash.success}} 4 |
5 | {{end}} 6 | 7 | {{if or .errors .flash.error}} 8 |
9 | {{if .flash.error}} 10 | {{.flash.error}} 11 | {{end}} 12 | 17 |
18 | {{end}} 19 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/footer.html: -------------------------------------------------------------------------------- 1 | {{if eq .RunMode "dev"}} 2 | {{template "debug.html" .}} 3 | {{end}} 4 | 5 | 6 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/app/views/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{.title}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{range .moreStyles}} 13 | 14 | {{end}} 15 | {{range .moreScripts}} 16 | 17 | {{end}} 18 | 19 | 20 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/conf/app.conf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Revel configuration file 3 | # More info at http://revel.github.io/manual/appconf.html 4 | ################################################################################ 5 | 6 | # Sets `revel.AppName` for use in-app. 7 | # Example: 8 | # `if revel.AppName {...}` 9 | app.name = railAPIRevel 10 | 11 | # A secret string which is passed to cryptographically sign the cookie to prevent 12 | # (and detect) user modification. 13 | # Keep this string secret or users will be able to inject arbitrary cookie values 14 | # into your application 15 | app.secret = VSSyD9rDBvgdxBzjVBdrKN5qGkuesunQd0ahIr1JENCBrjQAdMz1DUaz8AzwIy4I 16 | 17 | # Revel running behind proxy like nginx, haproxy, etc. 18 | app.behind.proxy = false 19 | 20 | 21 | # The IP address on which to listen. 22 | http.addr = 23 | 24 | # The port on which to listen. 25 | http.port = 8000 26 | 27 | # Whether to use SSL or not. 28 | http.ssl = false 29 | 30 | # Path to an X509 certificate file, if using SSL. 31 | #http.sslcert = 32 | 33 | # Path to an X509 certificate key, if using SSL. 34 | #http.sslkey = 35 | 36 | 37 | # Timeout specifies a time limit for request (in seconds) made by a single client. 38 | # A Timeout of zero means no timeout. 39 | http.timeout.read = 90 40 | http.timeout.write = 60 41 | 42 | 43 | # For any cookies set by Revel (Session,Flash,Error) these properties will set 44 | # the fields of: 45 | # http://golang.org/pkg/net/http/#Cookie 46 | # 47 | # Each cookie set by Revel is prefixed with this string. 48 | cookie.prefix = REVEL 49 | 50 | # A secure cookie has the secure attribute enabled and is only used via HTTPS, 51 | # ensuring that the cookie is always encrypted when transmitting from client to 52 | # server. This makes the cookie less likely to be exposed to cookie theft via 53 | # eavesdropping. 54 | # 55 | # Defaults to false. If 'http.ssl' is enabled, this will be defaulted to true. 56 | # This should only be true when Revel is handling SSL connections. If you are 57 | # using a proxy in front of revel (Nginx, Apache, etc), then this should be left 58 | # as false. 59 | # cookie.secure = false 60 | 61 | # Limit cookie access to a given domain. 62 | #cookie.domain = 63 | 64 | # Define when your session cookie expires. 65 | # Values: 66 | # "720h" 67 | # A time duration (http://golang.org/pkg/time/#ParseDuration) after which 68 | # the cookie expires and the session is invalid. 69 | # "session" 70 | # Sets a session cookie which invalidates the session when the user close 71 | # the browser. 72 | session.expires = 720h 73 | 74 | 75 | # The date format used by Revel. Possible formats defined by the Go `time` 76 | # package (http://golang.org/pkg/time/#Parse) 77 | format.date = 2006-01-02 78 | format.datetime = 2006-01-02 15:04 79 | 80 | 81 | # Determines whether the template rendering should use chunked encoding. 82 | # Chunked encoding can decrease the time to first byte on the client side by 83 | # sending data before the entire template has been fully rendered. 84 | results.chunked = false 85 | 86 | 87 | # Prefixes for each log message line. 88 | # User can override these prefix values within any section 89 | # For e.g: [dev], [prod], etc 90 | log.trace.prefix = "TRACE " 91 | log.info.prefix = "INFO " 92 | log.warn.prefix = "WARN " 93 | log.error.prefix = "ERROR " 94 | 95 | 96 | # The default language of this application. 97 | i18n.default_language = en 98 | 99 | # The default format when message is missing. 100 | # The original message shows in %s 101 | #i18n.unknown_format = "??? %s ???" 102 | 103 | 104 | # Module to serve static content such as CSS, JavaScript and Media files 105 | # Allows Routes like this: 106 | # `Static.ServeModule("modulename","public")` 107 | module.static=github.com/revel/modules/static 108 | 109 | 110 | 111 | ################################################################################ 112 | 113 | # Section: dev 114 | # This section is evaluated when running Revel in dev mode. Like so: 115 | # `revel run path/to/myapp` 116 | [dev] 117 | 118 | # This sets `revel.DevMode` for use in-app. 119 | # Example: 120 | # `if revel.DevMode {...}` 121 | # or in your templates with 122 | # `` 123 | # Values: 124 | # "true" 125 | # Sets `DevMode` to `true`. 126 | # "false" 127 | # Sets `DevMode` to `false`. 128 | mode.dev = true 129 | 130 | 131 | # Pretty print JSON/XML when calling RenderJSON/RenderXML 132 | # Values: 133 | # "true" 134 | # Enables pretty printing. 135 | # "false" 136 | # Disables pretty printing. 137 | results.pretty = true 138 | 139 | 140 | # Watch your applicaton files for changes and automatically rebuild 141 | # Values: 142 | # "true" 143 | # Enables auto rebuilding. 144 | # "false" 145 | # Disables auto rebuilding. 146 | watch = true 147 | 148 | 149 | # Define when to rebuild new changes. 150 | # Values: 151 | # "normal" 152 | # Rebuild when a new request is received and changes have been detected. 153 | # "eager" 154 | # Rebuild as soon as changes are detected. 155 | watch.mode = normal 156 | 157 | # Watch the entire `$GOPATH` for changes. 158 | # Values: 159 | # "true" 160 | # Includes `$GOPATH` in watch path. 161 | # "false" 162 | # Excludes `$GOPATH` from watch path. Default value. 163 | #watch.gopath = true 164 | 165 | 166 | # Module to run code tests in the browser 167 | # See: 168 | # http://revel.github.io/manual/testing.html 169 | module.testrunner = github.com/revel/modules/testrunner 170 | 171 | 172 | # Where to log the various Revel logs 173 | # Values: 174 | # "off" 175 | # Disable log output. 176 | # "stdout" 177 | # Log to OS's standard output. 178 | # "stderr" 179 | # Log to Os's standard error output. Default value. 180 | # "relative/path/to/log" 181 | # Log to file. 182 | log.trace.output = off 183 | log.info.output = stderr 184 | log.warn.output = stderr 185 | log.error.output = stderr 186 | 187 | 188 | # Revel log flags. Possible flags defined by the Go `log` package. Go log is 189 | # "Bits OR'ed together to control what's printed 190 | # See: 191 | # https://golang.org/pkg/log/#pkg-constants 192 | # Values: 193 | # "0" 194 | # Just log the message, turn off the flags. 195 | # "3" 196 | # log.LstdFlags (log.Ldate|log.Ltime) 197 | # "19" 198 | # log.Ldate|log.Ltime|log.Lshortfile 199 | # "23" 200 | # log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile 201 | log.trace.flags = 19 202 | log.info.flags = 19 203 | log.warn.flags = 19 204 | log.error.flags = 19 205 | 206 | 207 | # Revel request access log 208 | # Access log line format: 209 | # RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath 210 | # Sample format: 211 | # 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET / 212 | log.request.output = stderr 213 | 214 | 215 | 216 | ################################################################################ 217 | # Section: prod 218 | # This section is evaluated when running Revel in production mode. Like so: 219 | # `revel run path/to/myapp prod` 220 | # See: 221 | # [dev] section for documentation of the various settings 222 | [prod] 223 | 224 | mode.dev = false 225 | 226 | results.pretty = false 227 | 228 | watch = false 229 | 230 | module.testrunner = 231 | 232 | log.trace.output = off 233 | log.info.output = off 234 | log.warn.output = log/%(app.name)s.log 235 | log.error.output = log/%(app.name)s.log 236 | 237 | # Revel log flags. Possible flags defined by the Go `log` package, 238 | # please refer https://golang.org/pkg/log/#pkg-constants 239 | # Go log is "Bits or'ed together to control what's printed" 240 | # Examples: 241 | # 0 => just log the message, turn off the flags 242 | # 3 => log.LstdFlags (log.Ldate|log.Ltime) 243 | # 19 => log.Ldate|log.Ltime|log.Lshortfile 244 | # 23 => log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile 245 | log.trace.flags = 3 246 | log.info.flags = 3 247 | log.warn.flags = 3 248 | log.error.flags = 3 249 | 250 | 251 | # Revel request access log 252 | # Access log line format: 253 | # RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath 254 | # Sample format: 255 | # 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET / 256 | # Example: 257 | # log.request.output = %(app.name)s-request.log 258 | log.request.output = off 259 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/conf/routes: -------------------------------------------------------------------------------- 1 | # Routes Config 2 | # 3 | # This file defines all application routes (Higher priority routes first) 4 | # 5 | 6 | module:testrunner 7 | # module:jobs 8 | 9 | 10 | GET /v1/trains/:train-id App.GetTrain 11 | POST /v1/trains App.CreateTrain 12 | DELETE /v1/trains/:train-id App.RemoveTrain 13 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/messages/sample.en: -------------------------------------------------------------------------------- 1 | # Sample messages file for the English language (en) 2 | # Message file extensions should be ISO 639-1 codes (http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) 3 | # Sections within each message file can optionally override the defaults using ISO 3166-1 alpha-2 codes (http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) 4 | # See also: 5 | # - http://www.rfc-editor.org/rfc/bcp/bcp47.txt 6 | # - http://www.w3.org/International/questions/qa-accept-lang-locales 7 | 8 | -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Building-RESTful-Web-Services-with-Go/209a15d54dd67ef822420c94db7581a0bd1de23d/Chapter04/railAPIRevel/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Building-RESTful-Web-Services-with-Go/209a15d54dd67ef822420c94db7581a0bd1de23d/Chapter04/railAPIRevel/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Building-RESTful-Web-Services-with-Go/209a15d54dd67ef822420c94db7581a0bd1de23d/Chapter04/railAPIRevel/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/public/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Building-RESTful-Web-Services-with-Go/209a15d54dd67ef822420c94db7581a0bd1de23d/Chapter04/railAPIRevel/public/img/favicon.png -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/public/js/bootstrap-3.3.6.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /Chapter04/railAPIRevel/tests/apptest.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "github.com/revel/revel/testing" 5 | ) 6 | 7 | type AppTest struct { 8 | testing.TestSuite 9 | } 10 | 11 | func (t *AppTest) Before() { 12 | println("Set up") 13 | } 14 | 15 | func (t *AppTest) TestThatIndexPageWorks() { 16 | t.Get("/") 17 | t.AssertOk() 18 | t.AssertContentType("text/html; charset=utf-8") 19 | } 20 | 21 | func (t *AppTest) After() { 22 | println("Tear down") 23 | } 24 | -------------------------------------------------------------------------------- /Chapter04/sqliteFundamentals.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | 7 | _ "github.com/mattn/go-sqlite3" 8 | ) 9 | 10 | // Book is a placeholder for book 11 | type Book struct { 12 | id int 13 | name string 14 | author string 15 | } 16 | 17 | func main() { 18 | db, err := sql.Open("sqlite3", "./books.db") 19 | log.Println(db) 20 | if err != nil { 21 | log.Println(err) 22 | } 23 | // Create table 24 | statement, err := db.Prepare("CREATE TABLE IF NOT EXISTS books (id INTEGER PRIMARY KEY, isbn INTEGER, author VARCHAR(64), name VARCHAR(64) NULL)") 25 | if err != nil { 26 | log.Println("Error in creating table") 27 | } else { 28 | log.Println("Successfully created table books!") 29 | } 30 | statement.Exec() 31 | // Create 32 | statement, _ = db.Prepare("INSERT INTO books (name, author, isbn) VALUES (?, ?, ?)") 33 | statement.Exec("A Tale of Two Cities", "Charles Dickens", 140430547) 34 | log.Println("Inserted the book into database!") 35 | 36 | // Read 37 | rows, _ := db.Query("SELECT id, name, author FROM books") 38 | var tempBook Book 39 | for rows.Next() { 40 | rows.Scan(&tempBook.id, &tempBook.name, &tempBook.author) 41 | log.Printf("ID:%d, Book:%s, Author:%s\n", tempBook.id, tempBook.name, tempBook.author) 42 | } 43 | // Update 44 | statement, _ = db.Prepare("update books set name=? where id=?") 45 | statement.Exec("The Tale of Two Cities", 1) 46 | log.Println("Successfully updated the book in database!") 47 | 48 | //Delete 49 | statement, _ = db.Prepare("delete from books where id=?") 50 | statement.Exec(1) 51 | log.Println("Successfully deleted the book in database!") 52 | } 53 | -------------------------------------------------------------------------------- /Chapter05/category.json: -------------------------------------------------------------------------------- 1 | { 2 | _id: ObjectId("6d3b56900f41ead96110cf4f"), 3 | name: "Casual Shirts", 4 | description: "All casual shirts for men", 5 | slug: "casual-shirts", 6 | parent_categories: [{ 7 | slug: "home" 8 | name: "Home", 9 | _id: ObjectId("3d3b10f41efad96g110vcf4f"), 10 | }, 11 | { 12 | slug: "shirts" 13 | name: "Shirts", 14 | _id: ObjectId("603d3eb0ft41ead96110cf4f"), 15 | }] 16 | } -------------------------------------------------------------------------------- /Chapter05/mgoIntro.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "gopkg.in/mgo.v2" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | type Movie struct { 11 | Name string `bson:"name"` 12 | Year string `bson:"year"` 13 | Directors []string `bson:"directors"` 14 | Writers []string `bson:"writers"` 15 | BoxOffice `bson:"boxOffice"` 16 | } 17 | 18 | type BoxOffice struct { 19 | Budget uint64 `bson:"budget"` 20 | Gross uint64 `bson:"gross"` 21 | } 22 | 23 | func main() { 24 | session, err := mgo.Dial("127.0.0.1") 25 | if err != nil { 26 | panic(err) 27 | } 28 | defer session.Close() 29 | 30 | c := session.DB("appdb").C("movies") 31 | 32 | darkNight := &Movie{ 33 | Name: "The Dark Knight", 34 | Year: "2008", 35 | Directors: []string{"Christopher Nolan"}, 36 | Writers: []string{"Jonathan Nolan", "Christopher Nolan"}, 37 | BoxOffice: BoxOffice{ 38 | Budget: 185000000, 39 | Gross: 533316061, 40 | }, 41 | } 42 | err = c.Insert(darkNight) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | result := Movie{} 48 | err = c.Find(bson.M{"boxOffice.budget": bson.M{"$gt": 150000000}}).One(&result) 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | fmt.Println("Movie:", result.Name) 54 | } -------------------------------------------------------------------------------- /Chapter05/movieAPI.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/gorilla/mux" 11 | mgo "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | // DB stores the database session imformation. Needs to be initialized once 16 | type DB struct { 17 | session *mgo.Session 18 | collection *mgo.Collection 19 | } 20 | 21 | type Movie struct { 22 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 23 | Name string `json:"name" bson:"name"` 24 | Year string `json:"year" bson:"year"` 25 | Directors []string `json:"directors" bson:"directors"` 26 | Writers []string `json:"writers" bson:"writers"` 27 | BoxOffice BoxOffice `json:"boxOffice" bson:"boxOffice"` 28 | } 29 | 30 | type BoxOffice struct { 31 | Budget uint64 `json:"budget" bson:"budget"` 32 | Gross uint64 `json:"gross" bson:"gross"` 33 | } 34 | 35 | // GetMovie fetches a movie with a given ID 36 | func (db *DB) GetMovie(w http.ResponseWriter, r *http.Request) { 37 | vars := mux.Vars(r) 38 | w.WriteHeader(http.StatusOK) 39 | var movie Movie 40 | err := db.collection.Find(bson.M{"_id": bson.ObjectIdHex(vars["id"])}).One(&movie) 41 | if err != nil { 42 | w.Write([]byte(err.Error())) 43 | } else { 44 | w.Header().Set("Content-Type", "application/json") 45 | response, _ := json.Marshal(movie) 46 | w.Write(response) 47 | } 48 | 49 | } 50 | 51 | // PostMovie adds a new movie to our MongoDB collection 52 | func (db *DB) PostMovie(w http.ResponseWriter, r *http.Request) { 53 | var movie Movie 54 | postBody, _ := ioutil.ReadAll(r.Body) 55 | json.Unmarshal(postBody, &movie) 56 | // Create an Hash ID to insert 57 | movie.ID = bson.NewObjectId() 58 | err := db.collection.Insert(movie) 59 | if err != nil { 60 | w.Write([]byte(err.Error())) 61 | } else { 62 | w.Header().Set("Content-Type", "application/json") 63 | response, _ := json.Marshal(movie) 64 | w.Write(response) 65 | } 66 | } 67 | 68 | func main() { 69 | session, err := mgo.Dial("127.0.0.1") 70 | c := session.DB("appdb").C("movies") 71 | db := &DB{session: session, collection: c} 72 | if err != nil { 73 | panic(err) 74 | } 75 | defer session.Close() 76 | // Create a new router 77 | r := mux.NewRouter() 78 | // Attach an elegant path with handler 79 | r.HandleFunc("/v1/movies/{id:[a-zA-Z0-9]*}", db.GetMovie).Methods("GET") 80 | r.HandleFunc("/v1/movies", db.PostMovie).Methods("POST") 81 | srv := &http.Server{ 82 | Handler: r, 83 | Addr: "127.0.0.1:8000", 84 | // Good practice: enforce timeouts for servers you create! 85 | WriteTimeout: 15 * time.Second, 86 | ReadTimeout: 15 * time.Second, 87 | } 88 | log.Fatal(srv.ListenAndServe()) 89 | } 90 | -------------------------------------------------------------------------------- /Chapter05/movieAPI_updated.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/gorilla/mux" 11 | mgo "gopkg.in/mgo.v2" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | // DB stores the database session imformation. Needs to be initialized once 16 | type DB struct { 17 | session *mgo.Session 18 | collection *mgo.Collection 19 | } 20 | 21 | type Movie struct { 22 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 23 | Name string `json:"name" bson:"name"` 24 | Year string `json:"year" bson:"year"` 25 | Directors []string `json:"directors" bson:"directors"` 26 | Writers []string `json:"writers" bson:"writers"` 27 | BoxOffice BoxOffice `json:"boxOffice" bson:"boxOffice"` 28 | } 29 | 30 | type BoxOffice struct { 31 | Budget uint64 `json:"budget" bson:"budget"` 32 | Gross uint64 `json:"gross" bson:"gross"` 33 | } 34 | 35 | // GetMovie fetches a movie with a given ID 36 | func (db *DB) GetMovie(w http.ResponseWriter, r *http.Request) { 37 | vars := mux.Vars(r) 38 | var movie Movie 39 | err := db.collection.Find(bson.M{"_id": bson.ObjectIdHex(vars["id"])}).One(&movie) 40 | if err != nil { 41 | w.WriteHeader(http.StatusOK) 42 | w.Write([]byte(err.Error())) 43 | } else { 44 | w.Header().Set("Content-Type", "application/json") 45 | response, _ := json.Marshal(movie) 46 | w.Write(response) 47 | } 48 | 49 | } 50 | 51 | // PostMovie adds a new movie to our MongoDB collection 52 | func (db *DB) PostMovie(w http.ResponseWriter, r *http.Request) { 53 | var movie Movie 54 | postBody, _ := ioutil.ReadAll(r.Body) 55 | json.Unmarshal(postBody, &movie) 56 | // Create an Hash ID to insert 57 | movie.ID = bson.NewObjectId() 58 | err := db.collection.Insert(movie) 59 | if err != nil { 60 | w.Write([]byte(err.Error())) 61 | } else { 62 | w.Header().Set("Content-Type", "application/json") 63 | response, _ := json.Marshal(movie) 64 | w.Write(response) 65 | } 66 | } 67 | 68 | // UpdateMovie modifies the data of given resource 69 | func (db *DB) UpdateMovie(w http.ResponseWriter, r *http.Request) { 70 | vars := mux.Vars(r) 71 | var movie Movie 72 | putBody, _ := ioutil.ReadAll(r.Body) 73 | json.Unmarshal(putBody, &movie) 74 | // Create an Hash ID to insert 75 | err := db.collection.Update(bson.M{"_id": bson.ObjectIdHex(vars["id"])}, bson.M{"$set": &movie}) 76 | if err != nil { 77 | w.WriteHeader(http.StatusOK) 78 | w.Write([]byte(err.Error())) 79 | } else { 80 | w.Header().Set("Content-Type", "text") 81 | w.Write([]byte("Updated succesfully!")) 82 | } 83 | } 84 | 85 | // DeleteMovie modifies the data of given resource 86 | func (db *DB) DeleteMovie(w http.ResponseWriter, r *http.Request) { 87 | vars := mux.Vars(r) 88 | // Create an Hash ID to insert 89 | err := db.collection.Remove(bson.M{"_id": bson.ObjectIdHex(vars["id"])}) 90 | if err != nil { 91 | w.WriteHeader(http.StatusOK) 92 | w.Write([]byte(err.Error())) 93 | } else { 94 | w.Header().Set("Content-Type", "text") 95 | w.Write([]byte("Deleted succesfully!")) 96 | } 97 | } 98 | 99 | func main() { 100 | session, err := mgo.Dial("127.0.0.1") 101 | c := session.DB("appdb").C("movies") 102 | db := &DB{session: session, collection: c} 103 | if err != nil { 104 | panic(err) 105 | } 106 | defer session.Close() 107 | // Create a new router 108 | r := mux.NewRouter() 109 | // Attach an elegant path with handler 110 | r.HandleFunc("/v1/movies/{id:[a-zA-Z0-9]*}", db.GetMovie).Methods("GET") 111 | r.HandleFunc("/v1/movies", db.PostMovie).Methods("POST") 112 | r.HandleFunc("/v1/movies/{id:[a-zA-Z0-9]*}", db.UpdateMovie).Methods("PUT") 113 | r.HandleFunc("/v1/movies/{id:[a-zA-Z0-9]*}", db.DeleteMovie).Methods("DELETE") 114 | srv := &http.Server{ 115 | Handler: r, 116 | Addr: "127.0.0.1:8000", 117 | // Good practice: enforce timeouts for servers you create! 118 | WriteTimeout: 15 * time.Second, 119 | ReadTimeout: 15 * time.Second, 120 | } 121 | log.Fatal(srv.ListenAndServe()) 122 | } 123 | -------------------------------------------------------------------------------- /Chapter05/order.json: -------------------------------------------------------------------------------- 1 | { 2 | _id: ObjectId(), 3 | user: ObjectId("4fcf3eb0ft41ead96110"), 4 | state: "cart", 5 | item_queue: [{ 6 | item: ObjectId("59603d3b0f41ead96110cf4f"), 7 | quantity: 1, 8 | cost: 23 9 | }], 10 | shipping_address: { 11 | type: "work" 12 | street: "241 Indian Spring St", 13 | city: "Pittsburg", 14 | state: "California", 15 | pincode: 94565 16 | }, 17 | total: 23, 18 | } -------------------------------------------------------------------------------- /Chapter05/product.json: -------------------------------------------------------------------------------- 1 | { 2 | _id: ObjectId("59603d3b0f41ead96110cf4f"), 3 | sku: 1022, 4 | slug: "highlander-shirt-223", 5 | name: "Highlander casual shirt", 6 | description: "A nice looking casual shirt for men", 7 | details: { 8 | model_number: 235476, 9 | manufacturer: "HighLander", 10 | color: "light blue", 11 | mfg_date: new Date(2017, 4, 1), 12 | size: 40 13 | }, 14 | reviews: 3, 15 | pricing: { 16 | cost: 23, 17 | retail: 29 18 | }, 19 | categories: { 20 | ObjectId("3d3b10f41efad96g110vcf4f"), 21 | ObjectId("603d3eb0ft41ead96110cf4f") 22 | }, 23 | tags: ["shirts", "men", "clothing"], 24 | reviews: { 25 | ObjectId("3bd310f41efad96g110vcf4f"), 26 | ObjectId("f4e603d3eb0ft41ead96110c"), 27 | ObjectId("96g3bd310f41efad110vcf4g") 28 | } 29 | } -------------------------------------------------------------------------------- /Chapter05/review.json: -------------------------------------------------------------------------------- 1 | { 2 | _id: ObjectId("5tcf3eb0ft41ead96110"), 3 | product: ObjectId("4fcf3eb0ft41ead96110"), 4 | posted_date: new Date(2017, 2, 6), 5 | title: "Overall satisfied with product", 6 | body: "The product is good and durable. After dry wash, the color hasn't changed much", 7 | user: ObjectId(), 8 | rating: 4, 9 | upvotes: 3, 10 | downvotes: 0, 11 | upvoters: [ObjectId("41ea5tcf3eb0ftd9233476hg"), 12 | ObjectId("507f1f77bcf86cd799439011"), 13 | ObjectId("54f113fffba522406c9cc20f") 14 | ], 15 | downvoters: [] 16 | } -------------------------------------------------------------------------------- /Chapter05/user.json: -------------------------------------------------------------------------------- 1 | { 2 | _id: ObjectId("4fcf3eb0ft41ead96110"), 3 | username: "John", 4 | email_address: "john.p@gmail.com", 5 | password: "5kj64k56hdfjkhdfkgdf98g79df7g9dfg", 6 | first_name: "John", 7 | last_name: "Pauling", 8 | address_multiple: [{ 9 | type: "home" 10 | street: "601 Sherwood Ave", 11 | city: "San Bernardino", 12 | state: "California", 13 | pincode: 94565 14 | }, 15 | { 16 | type: "work" 17 | street: "241 Indian Spring St", 18 | city: "Pittsburg", 19 | state: "California", 20 | pincode: 94565 21 | }] , 22 | payments: { 23 | name: "Paypal", 24 | auth: { 25 | token: "dfghjvbsclka76asdadn89" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Chapter06/grpc_example/datafiles/transaction.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: transaction.proto 3 | 4 | /* 5 | Package datafiles is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | transaction.proto 9 | 10 | It has these top-level messages: 11 | TransactionRequest 12 | TransactionResponse 13 | */ 14 | package datafiles 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | import ( 21 | context "golang.org/x/net/context" 22 | grpc "google.golang.org/grpc" 23 | ) 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | type TransactionRequest struct { 37 | From string `protobuf:"bytes,1,opt,name=from" json:"from,omitempty"` 38 | To string `protobuf:"bytes,2,opt,name=to" json:"to,omitempty"` 39 | Amount float32 `protobuf:"fixed32,3,opt,name=amount" json:"amount,omitempty"` 40 | } 41 | 42 | func (m *TransactionRequest) Reset() { *m = TransactionRequest{} } 43 | func (m *TransactionRequest) String() string { return proto.CompactTextString(m) } 44 | func (*TransactionRequest) ProtoMessage() {} 45 | func (*TransactionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 46 | 47 | func (m *TransactionRequest) GetFrom() string { 48 | if m != nil { 49 | return m.From 50 | } 51 | return "" 52 | } 53 | 54 | func (m *TransactionRequest) GetTo() string { 55 | if m != nil { 56 | return m.To 57 | } 58 | return "" 59 | } 60 | 61 | func (m *TransactionRequest) GetAmount() float32 { 62 | if m != nil { 63 | return m.Amount 64 | } 65 | return 0 66 | } 67 | 68 | type TransactionResponse struct { 69 | Confirmation bool `protobuf:"varint,1,opt,name=confirmation" json:"confirmation,omitempty"` 70 | } 71 | 72 | func (m *TransactionResponse) Reset() { *m = TransactionResponse{} } 73 | func (m *TransactionResponse) String() string { return proto.CompactTextString(m) } 74 | func (*TransactionResponse) ProtoMessage() {} 75 | func (*TransactionResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 76 | 77 | func (m *TransactionResponse) GetConfirmation() bool { 78 | if m != nil { 79 | return m.Confirmation 80 | } 81 | return false 82 | } 83 | 84 | func init() { 85 | proto.RegisterType((*TransactionRequest)(nil), "datafiles.TransactionRequest") 86 | proto.RegisterType((*TransactionResponse)(nil), "datafiles.TransactionResponse") 87 | } 88 | 89 | // Reference imports to suppress errors if they are not otherwise used. 90 | var _ context.Context 91 | var _ grpc.ClientConn 92 | 93 | // This is a compile-time assertion to ensure that this generated file 94 | // is compatible with the grpc package it is being compiled against. 95 | const _ = grpc.SupportPackageIsVersion4 96 | 97 | // Client API for MoneyTransaction service 98 | 99 | type MoneyTransactionClient interface { 100 | MakeTransaction(ctx context.Context, in *TransactionRequest, opts ...grpc.CallOption) (*TransactionResponse, error) 101 | } 102 | 103 | type moneyTransactionClient struct { 104 | cc *grpc.ClientConn 105 | } 106 | 107 | func NewMoneyTransactionClient(cc *grpc.ClientConn) MoneyTransactionClient { 108 | return &moneyTransactionClient{cc} 109 | } 110 | 111 | func (c *moneyTransactionClient) MakeTransaction(ctx context.Context, in *TransactionRequest, opts ...grpc.CallOption) (*TransactionResponse, error) { 112 | out := new(TransactionResponse) 113 | err := grpc.Invoke(ctx, "/datafiles.MoneyTransaction/MakeTransaction", in, out, c.cc, opts...) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return out, nil 118 | } 119 | 120 | // Server API for MoneyTransaction service 121 | 122 | type MoneyTransactionServer interface { 123 | MakeTransaction(context.Context, *TransactionRequest) (*TransactionResponse, error) 124 | } 125 | 126 | func RegisterMoneyTransactionServer(s *grpc.Server, srv MoneyTransactionServer) { 127 | s.RegisterService(&_MoneyTransaction_serviceDesc, srv) 128 | } 129 | 130 | func _MoneyTransaction_MakeTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 131 | in := new(TransactionRequest) 132 | if err := dec(in); err != nil { 133 | return nil, err 134 | } 135 | if interceptor == nil { 136 | return srv.(MoneyTransactionServer).MakeTransaction(ctx, in) 137 | } 138 | info := &grpc.UnaryServerInfo{ 139 | Server: srv, 140 | FullMethod: "/datafiles.MoneyTransaction/MakeTransaction", 141 | } 142 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 143 | return srv.(MoneyTransactionServer).MakeTransaction(ctx, req.(*TransactionRequest)) 144 | } 145 | return interceptor(ctx, in, info, handler) 146 | } 147 | 148 | var _MoneyTransaction_serviceDesc = grpc.ServiceDesc{ 149 | ServiceName: "datafiles.MoneyTransaction", 150 | HandlerType: (*MoneyTransactionServer)(nil), 151 | Methods: []grpc.MethodDesc{ 152 | { 153 | MethodName: "MakeTransaction", 154 | Handler: _MoneyTransaction_MakeTransaction_Handler, 155 | }, 156 | }, 157 | Streams: []grpc.StreamDesc{}, 158 | Metadata: "transaction.proto", 159 | } 160 | 161 | func init() { proto.RegisterFile("transaction.proto", fileDescriptor0) } 162 | 163 | var fileDescriptor0 = []byte{ 164 | // 191 bytes of a gzipped FileDescriptorProto 165 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2c, 0x29, 0x4a, 0xcc, 166 | 0x2b, 0x4e, 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4c, 167 | 0x49, 0x2c, 0x49, 0x4c, 0xcb, 0xcc, 0x49, 0x2d, 0x56, 0x0a, 0xe0, 0x12, 0x0a, 0x41, 0xc8, 0x07, 168 | 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x09, 0x71, 0xb1, 0xa4, 0x15, 0xe5, 0xe7, 0x4a, 0x30, 169 | 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, 0xd9, 0x42, 0x7c, 0x5c, 0x4c, 0x25, 0xf9, 0x12, 0x4c, 0x60, 170 | 0x11, 0xa6, 0x92, 0x7c, 0x21, 0x31, 0x2e, 0xb6, 0xc4, 0xdc, 0xfc, 0xd2, 0xbc, 0x12, 0x09, 0x66, 171 | 0x05, 0x46, 0x0d, 0xa6, 0x20, 0x28, 0x4f, 0xc9, 0x92, 0x4b, 0x18, 0xc5, 0xc4, 0xe2, 0x82, 0xfc, 172 | 0xbc, 0xe2, 0x54, 0x21, 0x25, 0x2e, 0x9e, 0xe4, 0xfc, 0xbc, 0xb4, 0xcc, 0xa2, 0xdc, 0x44, 0x90, 173 | 0x38, 0xd8, 0x68, 0x8e, 0x20, 0x14, 0x31, 0xa3, 0x34, 0x2e, 0x01, 0xdf, 0xfc, 0xbc, 0xd4, 0x4a, 174 | 0x24, 0xfd, 0x42, 0x41, 0x5c, 0xfc, 0xbe, 0x89, 0xd9, 0xa9, 0xc8, 0x42, 0xb2, 0x7a, 0x70, 0xf7, 175 | 0xeb, 0x61, 0x3a, 0x5e, 0x4a, 0x0e, 0x97, 0x34, 0xc4, 0x25, 0x4a, 0x0c, 0x49, 0x6c, 0xe0, 0x60, 176 | 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc7, 0x7c, 0x5e, 0x9b, 0x1b, 0x01, 0x00, 0x00, 177 | } 178 | -------------------------------------------------------------------------------- /Chapter06/grpc_example/datafiles/transaction.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package datafiles; 3 | 4 | message TransactionRequest { 5 | string from = 1; 6 | string to = 2; 7 | float amount = 3; 8 | } 9 | 10 | message TransactionResponse { 11 | bool confirmation = 1; 12 | } 13 | 14 | service MoneyTransaction { 15 | rpc MakeTransaction(TransactionRequest) returns (TransactionResponse) {} 16 | } 17 | -------------------------------------------------------------------------------- /Chapter06/grpc_example/grpcClient/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | pb "github.com/narenaryan/datafiles" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | address = "localhost:50051" 13 | ) 14 | 15 | func main() { 16 | // Set up a connection to the server. 17 | conn, err := grpc.Dial(address, grpc.WithInsecure()) 18 | if err != nil { 19 | log.Fatalf("Did not connect: %v", err) 20 | } 21 | defer conn.Close() 22 | c := pb.NewMoneyTransactionClient(conn) 23 | 24 | // Prepare data. Get this from clients like Frontend or App 25 | from := "1234" 26 | to := "5678" 27 | amount := float32(1250.75) 28 | 29 | // Contact the server and print out its response. 30 | r, err := c.MakeTransaction(context.Background(), &pb.TransactionRequest{From: from, 31 | To: to, Amount: amount}) 32 | if err != nil { 33 | log.Fatalf("Could not transact: %v", err) 34 | } 35 | log.Printf("Transaction confirmed: %t", r.Confirmation) 36 | } 37 | -------------------------------------------------------------------------------- /Chapter06/grpc_example/grpcServer/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | 7 | pb "github.com/narenaryan/datafiles" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/reflection" 11 | ) 12 | 13 | const ( 14 | port = ":50051" 15 | ) 16 | 17 | // server is used to create MoneyTransactionServer. 18 | type server struct{} 19 | 20 | // MakeTransaction implements MoneyTransactionServer.MakeTransaction 21 | func (s *server) MakeTransaction(ctx context.Context, in *pb.TransactionRequest) (*pb.TransactionResponse, error) { 22 | log.Printf("Got request for money Transfer....") 23 | log.Printf("Amount: %f, From A/c:%s, To A/c:%s", in.Amount, in.From, in.To) 24 | // Do database logic here.... 25 | return &pb.TransactionResponse{Confirmation: true}, nil 26 | } 27 | 28 | func main() { 29 | lis, err := net.Listen("tcp", port) 30 | if err != nil { 31 | log.Fatalf("Failed to listen: %v", err) 32 | } 33 | s := grpc.NewServer() 34 | pb.RegisterMoneyTransactionServer(s, &server{}) 35 | // Register reflection service on gRPC server. 36 | reflection.Register(s) 37 | if err := s.Serve(lis); err != nil { 38 | log.Fatalf("Failed to serve: %v", err) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Chapter06/protobufs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/golang/protobuf/proto" 7 | pb "github.com/narenaryan/protofiles" 8 | ) 9 | 10 | func main() { 11 | p := &pb.Person{ 12 | Id: 1234, 13 | Name: "Roger F", 14 | Email: "rf@example.com", 15 | Phones: []*pb.Person_PhoneNumber{ 16 | {Number: "555-4321", Type: pb.Person_HOME}, 17 | }, 18 | } 19 | 20 | p1 := &pb.Person{} 21 | body, _ := proto.Marshal(p) 22 | _ = proto.Unmarshal(body, p1) 23 | fmt.Println("Original struct loaded from proto file:", p, "\n") 24 | fmt.Println("Marshalled proto data: ", body, "\n") 25 | fmt.Println("Unmarshalled struct: ", p1) 26 | } -------------------------------------------------------------------------------- /Chapter06/protobufs/main_json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "encoding/json" 7 | pb "github.com/narenaryan/protofiles" 8 | ) 9 | 10 | func main() { 11 | p := &pb.Person{ 12 | Id: 1234, 13 | Name: "Roger F", 14 | Email: "rf@example.com", 15 | Phones: []*pb.Person_PhoneNumber{ 16 | {Number: "555-4321", Type: pb.Person_HOME}, 17 | }, 18 | } 19 | body, _ := json.Marshal(p) 20 | fmt.Println(string(body)) 21 | } -------------------------------------------------------------------------------- /Chapter06/protofiles/person.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: person.proto 3 | 4 | /* 5 | Package protofiles is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | person.proto 9 | 10 | It has these top-level messages: 11 | Person 12 | AddressBook 13 | */ 14 | package protofiles 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | // A compilation error at this line likely means your copy of the 28 | // proto package needs to be updated. 29 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 30 | 31 | type Person_PhoneType int32 32 | 33 | const ( 34 | Person_MOBILE Person_PhoneType = 0 35 | Person_HOME Person_PhoneType = 1 36 | Person_WORK Person_PhoneType = 2 37 | ) 38 | 39 | var Person_PhoneType_name = map[int32]string{ 40 | 0: "MOBILE", 41 | 1: "HOME", 42 | 2: "WORK", 43 | } 44 | var Person_PhoneType_value = map[string]int32{ 45 | "MOBILE": 0, 46 | "HOME": 1, 47 | "WORK": 2, 48 | } 49 | 50 | func (x Person_PhoneType) String() string { 51 | return proto.EnumName(Person_PhoneType_name, int32(x)) 52 | } 53 | func (Person_PhoneType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } 54 | 55 | type Person struct { 56 | Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` 57 | Id int32 `protobuf:"varint,2,opt,name=id" json:"id,omitempty"` 58 | Email string `protobuf:"bytes,3,opt,name=email" json:"email,omitempty"` 59 | Phones []*Person_PhoneNumber `protobuf:"bytes,4,rep,name=phones" json:"phones,omitempty"` 60 | } 61 | 62 | func (m *Person) Reset() { *m = Person{} } 63 | func (m *Person) String() string { return proto.CompactTextString(m) } 64 | func (*Person) ProtoMessage() {} 65 | func (*Person) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 66 | 67 | func (m *Person) GetName() string { 68 | if m != nil { 69 | return m.Name 70 | } 71 | return "" 72 | } 73 | 74 | func (m *Person) GetId() int32 { 75 | if m != nil { 76 | return m.Id 77 | } 78 | return 0 79 | } 80 | 81 | func (m *Person) GetEmail() string { 82 | if m != nil { 83 | return m.Email 84 | } 85 | return "" 86 | } 87 | 88 | func (m *Person) GetPhones() []*Person_PhoneNumber { 89 | if m != nil { 90 | return m.Phones 91 | } 92 | return nil 93 | } 94 | 95 | type Person_PhoneNumber struct { 96 | Number string `protobuf:"bytes,1,opt,name=number" json:"number,omitempty"` 97 | Type Person_PhoneType `protobuf:"varint,2,opt,name=type,enum=protofiles.Person_PhoneType" json:"type,omitempty"` 98 | } 99 | 100 | func (m *Person_PhoneNumber) Reset() { *m = Person_PhoneNumber{} } 101 | func (m *Person_PhoneNumber) String() string { return proto.CompactTextString(m) } 102 | func (*Person_PhoneNumber) ProtoMessage() {} 103 | func (*Person_PhoneNumber) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } 104 | 105 | func (m *Person_PhoneNumber) GetNumber() string { 106 | if m != nil { 107 | return m.Number 108 | } 109 | return "" 110 | } 111 | 112 | func (m *Person_PhoneNumber) GetType() Person_PhoneType { 113 | if m != nil { 114 | return m.Type 115 | } 116 | return Person_MOBILE 117 | } 118 | 119 | // Our address book file is just one of these. 120 | type AddressBook struct { 121 | People []*Person `protobuf:"bytes,1,rep,name=people" json:"people,omitempty"` 122 | } 123 | 124 | func (m *AddressBook) Reset() { *m = AddressBook{} } 125 | func (m *AddressBook) String() string { return proto.CompactTextString(m) } 126 | func (*AddressBook) ProtoMessage() {} 127 | func (*AddressBook) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 128 | 129 | func (m *AddressBook) GetPeople() []*Person { 130 | if m != nil { 131 | return m.People 132 | } 133 | return nil 134 | } 135 | 136 | func init() { 137 | proto.RegisterType((*Person)(nil), "protofiles.Person") 138 | proto.RegisterType((*Person_PhoneNumber)(nil), "protofiles.Person.PhoneNumber") 139 | proto.RegisterType((*AddressBook)(nil), "protofiles.AddressBook") 140 | proto.RegisterEnum("protofiles.Person_PhoneType", Person_PhoneType_name, Person_PhoneType_value) 141 | } 142 | 143 | func init() { proto.RegisterFile("person.proto", fileDescriptor0) } 144 | 145 | var fileDescriptor0 = []byte{ 146 | // 251 bytes of a gzipped FileDescriptorProto 147 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0x41, 0x4b, 0xc3, 0x40, 148 | 0x10, 0x85, 0xcd, 0x36, 0x5d, 0xec, 0x8b, 0x94, 0x30, 0x88, 0x04, 0x11, 0x09, 0x39, 0x05, 0x85, 149 | 0x20, 0x15, 0x04, 0x8f, 0x16, 0x0a, 0x8a, 0xd6, 0x94, 0x45, 0xe8, 0xb9, 0x25, 0x23, 0x06, 0x93, 150 | 0xec, 0x92, 0xad, 0x87, 0xde, 0xfc, 0xe9, 0x92, 0x4d, 0x50, 0x41, 0x3c, 0xed, 0x9b, 0x99, 0x8f, 151 | 0x79, 0x6f, 0x07, 0x47, 0x86, 0x5b, 0xab, 0x9b, 0xcc, 0xb4, 0x7a, 0xa7, 0x09, 0xee, 0x79, 0x2d, 152 | 0x2b, 0xb6, 0xc9, 0xa7, 0x80, 0x5c, 0xb9, 0x21, 0x11, 0xfc, 0x66, 0x53, 0x73, 0xe4, 0xc5, 0x5e, 153 | 0x3a, 0x51, 0x4e, 0xd3, 0x14, 0xa2, 0x2c, 0x22, 0x11, 0x7b, 0xe9, 0x58, 0x89, 0xb2, 0xa0, 0x63, 154 | 0x8c, 0xb9, 0xde, 0x94, 0x55, 0x34, 0x72, 0x50, 0x5f, 0xd0, 0x0d, 0xa4, 0x79, 0xd3, 0x0d, 0xdb, 155 | 0xc8, 0x8f, 0x47, 0x69, 0x30, 0x3b, 0xcf, 0x7e, 0x1c, 0xb2, 0x7e, 0x7b, 0xb6, 0xea, 0x80, 0xe7, 156 | 0x8f, 0x7a, 0xcb, 0xad, 0x1a, 0xe8, 0xd3, 0x35, 0x82, 0x5f, 0x6d, 0x3a, 0x81, 0x6c, 0x9c, 0x1a, 157 | 0x22, 0x0c, 0x15, 0x5d, 0xc1, 0xdf, 0xed, 0x0d, 0xbb, 0x18, 0xd3, 0xd9, 0xd9, 0x7f, 0xcb, 0x5f, 158 | 0xf6, 0x86, 0x95, 0x23, 0x93, 0x4b, 0x4c, 0xbe, 0x5b, 0x04, 0xc8, 0x65, 0x3e, 0x7f, 0x78, 0x5a, 159 | 0x84, 0x07, 0x74, 0x08, 0xff, 0x3e, 0x5f, 0x2e, 0x42, 0xaf, 0x53, 0xeb, 0x5c, 0x3d, 0x86, 0x22, 160 | 0xb9, 0x45, 0x70, 0x57, 0x14, 0x2d, 0x5b, 0x3b, 0xd7, 0xfa, 0x9d, 0x2e, 0x20, 0x0d, 0x6b, 0x53, 161 | 0x75, 0x87, 0xe8, 0x3e, 0x43, 0x7f, 0xfd, 0xd4, 0x40, 0x6c, 0xa5, 0x1b, 0x5d, 0x7f, 0x05, 0x00, 162 | 0x00, 0xff, 0xff, 0x7e, 0x71, 0x4d, 0x40, 0x60, 0x01, 0x00, 0x00, 163 | } 164 | -------------------------------------------------------------------------------- /Chapter06/protofiles/person.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package protofiles; 3 | 4 | message Person { 5 | string name = 1; 6 | int32 id = 2; // Unique ID number for this person. 7 | string email = 3; 8 | 9 | enum PhoneType { 10 | MOBILE = 0; 11 | HOME = 1; 12 | WORK = 2; 13 | } 14 | 15 | message PhoneNumber { 16 | string number = 1; 17 | PhoneType type = 2; 18 | } 19 | 20 | repeated PhoneNumber phones = 4; 21 | } 22 | 23 | // Our address book file is just one of these. 24 | message AddressBook { 25 | repeated Person people = 1; 26 | } -------------------------------------------------------------------------------- /Chapter06/serverPush/datafiles/transaction.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: transaction.proto 3 | 4 | /* 5 | Package datafiles is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | transaction.proto 9 | 10 | It has these top-level messages: 11 | TransactionRequest 12 | TransactionResponse 13 | */ 14 | package datafiles 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | import ( 21 | context "golang.org/x/net/context" 22 | grpc "google.golang.org/grpc" 23 | ) 24 | 25 | // Reference imports to suppress errors if they are not otherwise used. 26 | var _ = proto.Marshal 27 | var _ = fmt.Errorf 28 | var _ = math.Inf 29 | 30 | // This is a compile-time assertion to ensure that this generated file 31 | // is compatible with the proto package it is being compiled against. 32 | // A compilation error at this line likely means your copy of the 33 | // proto package needs to be updated. 34 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 35 | 36 | type TransactionRequest struct { 37 | From string `protobuf:"bytes,1,opt,name=from" json:"from,omitempty"` 38 | To string `protobuf:"bytes,2,opt,name=to" json:"to,omitempty"` 39 | Amount float32 `protobuf:"fixed32,3,opt,name=amount" json:"amount,omitempty"` 40 | } 41 | 42 | func (m *TransactionRequest) Reset() { *m = TransactionRequest{} } 43 | func (m *TransactionRequest) String() string { return proto.CompactTextString(m) } 44 | func (*TransactionRequest) ProtoMessage() {} 45 | func (*TransactionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 46 | 47 | func (m *TransactionRequest) GetFrom() string { 48 | if m != nil { 49 | return m.From 50 | } 51 | return "" 52 | } 53 | 54 | func (m *TransactionRequest) GetTo() string { 55 | if m != nil { 56 | return m.To 57 | } 58 | return "" 59 | } 60 | 61 | func (m *TransactionRequest) GetAmount() float32 { 62 | if m != nil { 63 | return m.Amount 64 | } 65 | return 0 66 | } 67 | 68 | type TransactionResponse struct { 69 | Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` 70 | Step int32 `protobuf:"varint,2,opt,name=step" json:"step,omitempty"` 71 | Description string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"` 72 | } 73 | 74 | func (m *TransactionResponse) Reset() { *m = TransactionResponse{} } 75 | func (m *TransactionResponse) String() string { return proto.CompactTextString(m) } 76 | func (*TransactionResponse) ProtoMessage() {} 77 | func (*TransactionResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 78 | 79 | func (m *TransactionResponse) GetStatus() string { 80 | if m != nil { 81 | return m.Status 82 | } 83 | return "" 84 | } 85 | 86 | func (m *TransactionResponse) GetStep() int32 { 87 | if m != nil { 88 | return m.Step 89 | } 90 | return 0 91 | } 92 | 93 | func (m *TransactionResponse) GetDescription() string { 94 | if m != nil { 95 | return m.Description 96 | } 97 | return "" 98 | } 99 | 100 | func init() { 101 | proto.RegisterType((*TransactionRequest)(nil), "datafiles.TransactionRequest") 102 | proto.RegisterType((*TransactionResponse)(nil), "datafiles.TransactionResponse") 103 | } 104 | 105 | // Reference imports to suppress errors if they are not otherwise used. 106 | var _ context.Context 107 | var _ grpc.ClientConn 108 | 109 | // This is a compile-time assertion to ensure that this generated file 110 | // is compatible with the grpc package it is being compiled against. 111 | const _ = grpc.SupportPackageIsVersion4 112 | 113 | // Client API for MoneyTransaction service 114 | 115 | type MoneyTransactionClient interface { 116 | MakeTransaction(ctx context.Context, in *TransactionRequest, opts ...grpc.CallOption) (MoneyTransaction_MakeTransactionClient, error) 117 | } 118 | 119 | type moneyTransactionClient struct { 120 | cc *grpc.ClientConn 121 | } 122 | 123 | func NewMoneyTransactionClient(cc *grpc.ClientConn) MoneyTransactionClient { 124 | return &moneyTransactionClient{cc} 125 | } 126 | 127 | func (c *moneyTransactionClient) MakeTransaction(ctx context.Context, in *TransactionRequest, opts ...grpc.CallOption) (MoneyTransaction_MakeTransactionClient, error) { 128 | stream, err := grpc.NewClientStream(ctx, &_MoneyTransaction_serviceDesc.Streams[0], c.cc, "/datafiles.MoneyTransaction/MakeTransaction", opts...) 129 | if err != nil { 130 | return nil, err 131 | } 132 | x := &moneyTransactionMakeTransactionClient{stream} 133 | if err := x.ClientStream.SendMsg(in); err != nil { 134 | return nil, err 135 | } 136 | if err := x.ClientStream.CloseSend(); err != nil { 137 | return nil, err 138 | } 139 | return x, nil 140 | } 141 | 142 | type MoneyTransaction_MakeTransactionClient interface { 143 | Recv() (*TransactionResponse, error) 144 | grpc.ClientStream 145 | } 146 | 147 | type moneyTransactionMakeTransactionClient struct { 148 | grpc.ClientStream 149 | } 150 | 151 | func (x *moneyTransactionMakeTransactionClient) Recv() (*TransactionResponse, error) { 152 | m := new(TransactionResponse) 153 | if err := x.ClientStream.RecvMsg(m); err != nil { 154 | return nil, err 155 | } 156 | return m, nil 157 | } 158 | 159 | // Server API for MoneyTransaction service 160 | 161 | type MoneyTransactionServer interface { 162 | MakeTransaction(*TransactionRequest, MoneyTransaction_MakeTransactionServer) error 163 | } 164 | 165 | func RegisterMoneyTransactionServer(s *grpc.Server, srv MoneyTransactionServer) { 166 | s.RegisterService(&_MoneyTransaction_serviceDesc, srv) 167 | } 168 | 169 | func _MoneyTransaction_MakeTransaction_Handler(srv interface{}, stream grpc.ServerStream) error { 170 | m := new(TransactionRequest) 171 | if err := stream.RecvMsg(m); err != nil { 172 | return err 173 | } 174 | return srv.(MoneyTransactionServer).MakeTransaction(m, &moneyTransactionMakeTransactionServer{stream}) 175 | } 176 | 177 | type MoneyTransaction_MakeTransactionServer interface { 178 | Send(*TransactionResponse) error 179 | grpc.ServerStream 180 | } 181 | 182 | type moneyTransactionMakeTransactionServer struct { 183 | grpc.ServerStream 184 | } 185 | 186 | func (x *moneyTransactionMakeTransactionServer) Send(m *TransactionResponse) error { 187 | return x.ServerStream.SendMsg(m) 188 | } 189 | 190 | var _MoneyTransaction_serviceDesc = grpc.ServiceDesc{ 191 | ServiceName: "datafiles.MoneyTransaction", 192 | HandlerType: (*MoneyTransactionServer)(nil), 193 | Methods: []grpc.MethodDesc{}, 194 | Streams: []grpc.StreamDesc{ 195 | { 196 | StreamName: "MakeTransaction", 197 | Handler: _MoneyTransaction_MakeTransaction_Handler, 198 | ServerStreams: true, 199 | }, 200 | }, 201 | Metadata: "transaction.proto", 202 | } 203 | 204 | func init() { proto.RegisterFile("transaction.proto", fileDescriptor0) } 205 | 206 | var fileDescriptor0 = []byte{ 207 | // 211 bytes of a gzipped FileDescriptorProto 208 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xdd, 0x4a, 0xc4, 0x30, 209 | 0x10, 0x85, 0x4d, 0xd4, 0x85, 0x8e, 0xe0, 0xcf, 0x08, 0x52, 0x04, 0xa5, 0xf4, 0x6a, 0xaf, 0x8a, 210 | 0xe8, 0x73, 0x2c, 0x48, 0xd8, 0x17, 0x88, 0xed, 0x14, 0x8b, 0x36, 0x13, 0x33, 0xd3, 0x0b, 0xdf, 211 | 0x5e, 0x1a, 0x8b, 0x56, 0x64, 0xef, 0x66, 0xce, 0x09, 0xdf, 0xc9, 0x19, 0xb8, 0xd2, 0xe4, 0x83, 212 | 0xf8, 0x56, 0x07, 0x0e, 0x4d, 0x4c, 0xac, 0x8c, 0x45, 0xe7, 0xd5, 0xf7, 0xc3, 0x3b, 0x49, 0xfd, 213 | 0x0c, 0xb8, 0xff, 0xf5, 0x1d, 0x7d, 0x4c, 0x24, 0x8a, 0x08, 0x27, 0x7d, 0xe2, 0xb1, 0x34, 0x95, 214 | 0xd9, 0x16, 0x2e, 0xcf, 0x78, 0x0e, 0x56, 0xb9, 0xb4, 0x59, 0xb1, 0xca, 0x78, 0x03, 0x1b, 0x3f, 215 | 0xf2, 0x14, 0xb4, 0x3c, 0xae, 0xcc, 0xd6, 0xba, 0x65, 0xab, 0x5b, 0xb8, 0xfe, 0x43, 0x94, 0xc8, 216 | 0x41, 0x68, 0x7e, 0x2e, 0xea, 0x75, 0x92, 0x05, 0xba, 0x6c, 0x73, 0x94, 0x28, 0xc5, 0x0c, 0x3e, 217 | 0x75, 0x79, 0xc6, 0x0a, 0xce, 0x3a, 0x92, 0x36, 0x0d, 0x71, 0x46, 0x64, 0x7e, 0xe1, 0xd6, 0xd2, 218 | 0xe3, 0x2b, 0x5c, 0xee, 0x38, 0xd0, 0xe7, 0x2a, 0x09, 0xf7, 0x70, 0xb1, 0xf3, 0x6f, 0xb4, 0x96, 219 | 0xee, 0x9a, 0x9f, 0xa6, 0xcd, 0xff, 0x9a, 0xb7, 0xf7, 0x87, 0xec, 0xef, 0x3f, 0xd7, 0x47, 0x0f, 220 | 0xe6, 0x65, 0x93, 0x4f, 0xf6, 0xf4, 0x15, 0x00, 0x00, 0xff, 0xff, 0x49, 0xf1, 0xd5, 0xc0, 0x47, 221 | 0x01, 0x00, 0x00, 222 | } 223 | -------------------------------------------------------------------------------- /Chapter06/serverPush/datafiles/transaction.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package datafiles; 3 | 4 | message TransactionRequest { 5 | string from = 1; 6 | string to = 2; 7 | float amount = 3; 8 | } 9 | 10 | message TransactionResponse { 11 | string status = 1; 12 | int32 step = 2; 13 | string description = 3; 14 | } 15 | 16 | service MoneyTransaction { 17 | rpc MakeTransaction(TransactionRequest) returns (stream TransactionResponse) {} 18 | } 19 | -------------------------------------------------------------------------------- /Chapter06/serverPush/grpcClient/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | 7 | pb "github.com/narenaryan/serverPush/datafiles" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | const ( 13 | address = "localhost:50051" 14 | ) 15 | 16 | // ReceiveStream listens to the stream contents and use them 17 | func ReceiveStream(client pb.MoneyTransactionClient, request *pb.TransactionRequest) { 18 | log.Println("Started listening to the server stream!") 19 | stream, err := client.MakeTransaction(context.Background(), request) 20 | if err != nil { 21 | log.Fatalf("%v.MakeTransaction(_) = _, %v", client, err) 22 | } 23 | // Listen to the stream of messages 24 | for { 25 | response, err := stream.Recv() 26 | if err == io.EOF { 27 | // If there are no more messages, get out of loop 28 | break 29 | } 30 | if err != nil { 31 | log.Fatalf("%v.MakeTransaction(_) = _, %v", client, err) 32 | } 33 | log.Printf("Status: %v, Operation: %v", response.Status, response.Description) 34 | } 35 | } 36 | 37 | func main() { 38 | // Set up a connection to the server. 39 | conn, err := grpc.Dial(address, grpc.WithInsecure()) 40 | if err != nil { 41 | log.Fatalf("Did not connect: %v", err) 42 | } 43 | defer conn.Close() 44 | client := pb.NewMoneyTransactionClient(conn) 45 | 46 | // Prepare data. Get this from clients like Front-end or Android App 47 | from := "1234" 48 | to := "5678" 49 | amount := float32(1250.75) 50 | 51 | // Contact the server and print out its response. 52 | ReceiveStream(client, &pb.TransactionRequest{From: from, 53 | To: to, Amount: amount}) 54 | } 55 | -------------------------------------------------------------------------------- /Chapter06/serverPush/grpcServer/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | pb "github.com/narenaryan/serverPush/datafiles" 10 | "google.golang.org/grpc" 11 | "google.golang.org/grpc/reflection" 12 | ) 13 | 14 | const ( 15 | port = ":50051" 16 | noOfSteps = 3 17 | ) 18 | 19 | // server is used to create MoneyTransactionServer. 20 | type server struct{} 21 | 22 | // MakeTransaction implements MoneyTransactionServer.MakeTransaction 23 | func (s *server) MakeTransaction(in *pb.TransactionRequest, stream pb.MoneyTransaction_MakeTransactionServer) error { 24 | log.Printf("Got request for money transfer....") 25 | log.Printf("Amount: $%f, From A/c:%s, To A/c:%s", in.Amount, in.From, in.To) 26 | // Send streams here 27 | for i := 0; i < noOfSteps; i++ { 28 | // Simulating I/O or Computation process using sleep........ 29 | // Usually this will be saving money transfer details in DB or 30 | // talk to the third party API 31 | time.Sleep(time.Second * 2) 32 | // Once task is done, send the successful message back to the client 33 | if err := stream.Send(&pb.TransactionResponse{Status: "good", 34 | Step: int32(i), 35 | Description: fmt.Sprintf("Description of step %d", int32(i))}); err != nil { 36 | log.Fatalf("%v.Send(%v) = %v", stream, "status", err) 37 | } 38 | } 39 | log.Printf("Successfully transfered amount $%v from %v to %v", in.Amount, in.From, in.To) 40 | return nil 41 | } 42 | 43 | func main() { 44 | lis, err := net.Listen("tcp", port) 45 | if err != nil { 46 | log.Fatalf("Failed to listen: %v", err) 47 | } 48 | // Create a new GRPC Server 49 | s := grpc.NewServer() 50 | // Register it with Proto service 51 | pb.RegisterMoneyTransactionServer(s, &server{}) 52 | // Register reflection service on gRPC server. 53 | reflection.Register(s) 54 | if err := s.Serve(lis); err != nil { 55 | log.Fatalf("Failed to serve: %v", err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Chapter07/base62example/base62/encodeutils.go: -------------------------------------------------------------------------------- 1 | package base62 2 | 3 | import ( 4 | "math" 5 | "strings" 6 | ) 7 | 8 | const base = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 9 | const b = 62 10 | 11 | // Function encodes the given database ID to a base62 string 12 | func ToBase62(num int) string{ 13 | r := num % b 14 | res := string(base[r]) 15 | div := num / b 16 | q := int(math.Floor(float64(div))) 17 | 18 | for q != 0 { 19 | r = q % b 20 | temp := q / b 21 | q = int(math.Floor(float64(temp))) 22 | res = string(base[int(r)]) + res 23 | } 24 | 25 | return string(res) 26 | } 27 | 28 | // Function decodes a given base62 string to datbase ID 29 | func ToBase10(str string) int{ 30 | res := 0 31 | for _, r := range str { 32 | res = (b * res) + strings.Index(base, string(r)) 33 | } 34 | return res 35 | } -------------------------------------------------------------------------------- /Chapter07/base62example/usebase62.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | base62 "github.com/narenaryan/base62" 6 | ) 7 | 8 | func main() { 9 | x := 100 10 | base62String := base62.ToBase62(x) 11 | log.Println(base62String) 12 | normalNumber := base62.ToBase10(base62String) 13 | log.Println(normalNumber) 14 | } -------------------------------------------------------------------------------- /Chapter07/basicexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "github.com/narenaryan/models" 6 | ) 7 | 8 | 9 | func main() { 10 | db, err := models.InitDB() 11 | if err != nil { 12 | log.Println(db) 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter07/basicexample/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | _ "github.com/lib/pq" 7 | ) 8 | 9 | 10 | func InitDB() (*sql.DB, error) { 11 | var err error 12 | db, err := sql.Open("postgres", "postgres://naren:passme123@localhost/mydb?sslmode=disable") 13 | if err != nil { 14 | return nil, err 15 | } else { 16 | // Create model for our URL service 17 | stmt, err := db.Prepare("CREATE TABLE WEB_URL(ID SERIAL PRIMARY KEY, URL TEXT NOT NULL);") 18 | if err != nil { 19 | log.Println(err) 20 | return nil, err 21 | } 22 | res, err := stmt.Exec() 23 | log.Println(res) 24 | if err != nil { 25 | log.Println(err) 26 | return nil, err 27 | } 28 | return db, nil 29 | } 30 | } -------------------------------------------------------------------------------- /Chapter07/jsonstore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/gorilla/mux" 11 | "github.com/jinzhu/gorm" 12 | _ "github.com/lib/pq" 13 | "github.com/narenaryan/jsonstore/models" 14 | ) 15 | 16 | // DB stores the database session imformation. Needs to be initialized once 17 | type DBClient struct { 18 | db *gorm.DB 19 | } 20 | 21 | // UserResponse is the response to be send back for User 22 | type UserResponse struct { 23 | User models.User `json:"user"` 24 | Data interface{} `json:"data"` 25 | } 26 | 27 | // GetUsersByFirstName fetches the original URL for the given encoded(short) string 28 | func (driver *DBClient) GetUsersByFirstName(w http.ResponseWriter, r *http.Request) { 29 | var users []models.User 30 | name := r.FormValue("first_name") 31 | // Handle response details 32 | var query = "select * from \"user\" where data->>'first_name'=?" 33 | driver.db.Raw(query, name).Scan(&users) 34 | w.WriteHeader(http.StatusOK) 35 | w.Header().Set("Content-Type", "application/json") 36 | //responseMap := map[string]interface{}{"url": ""} 37 | respJSON, _ := json.Marshal(users) 38 | w.Write(respJSON) 39 | } 40 | 41 | // GetUser fetches the original URL for the given encoded(short) string 42 | func (driver *DBClient) GetUser(w http.ResponseWriter, r *http.Request) { 43 | var user = models.User{} 44 | vars := mux.Vars(r) 45 | // Handle response details 46 | driver.db.First(&user, vars["id"]) 47 | var userData interface{} 48 | // Unmarshal JSON string to interface 49 | json.Unmarshal([]byte(user.Data), &userData) 50 | var response = UserResponse{User: user, Data: userData} 51 | w.WriteHeader(http.StatusOK) 52 | w.Header().Set("Content-Type", "application/json") 53 | //responseMap := map[string]interface{}{"url": ""} 54 | respJSON, _ := json.Marshal(response) 55 | w.Write(respJSON) 56 | } 57 | 58 | // PostUser adds URL to DB and gives back shortened string 59 | func (driver *DBClient) PostUser(w http.ResponseWriter, r *http.Request) { 60 | var user = models.User{} 61 | postBody, _ := ioutil.ReadAll(r.Body) 62 | user.Data = string(postBody) 63 | driver.db.Save(&user) 64 | responseMap := map[string]interface{}{"id": user.ID} 65 | var err string = "" 66 | if err != "" { 67 | w.Write([]byte("yes")) 68 | } else { 69 | w.Header().Set("Content-Type", "application/json") 70 | response, _ := json.Marshal(responseMap) 71 | w.Write(response) 72 | } 73 | } 74 | 75 | func main() { 76 | db, err := models.InitDB() 77 | if err != nil { 78 | panic(err) 79 | } 80 | dbclient := &DBClient{db: db} 81 | if err != nil { 82 | panic(err) 83 | } 84 | defer db.Close() 85 | // Create a new router 86 | r := mux.NewRouter() 87 | // Attach an elegant path with handler 88 | r.HandleFunc("/v1/user/{id:[a-zA-Z0-9]*}", dbclient.GetUser).Methods("GET") 89 | r.HandleFunc("/v1/user", dbclient.PostUser).Methods("POST") 90 | r.HandleFunc("/v1/user", dbclient.GetUsersByFirstName).Methods("GET") 91 | srv := &http.Server{ 92 | Handler: r, 93 | Addr: "127.0.0.1:8000", 94 | // Good practice: enforce timeouts for servers you create! 95 | WriteTimeout: 15 * time.Second, 96 | ReadTimeout: 15 * time.Second, 97 | } 98 | log.Fatal(srv.ListenAndServe()) 99 | } 100 | -------------------------------------------------------------------------------- /Chapter07/jsonstore/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | _ "github.com/lib/pq" 6 | ) 7 | 8 | type User struct { 9 | gorm.Model 10 | Orders []Order 11 | Data string `sql:"type:JSONB NOT NULL DEFAULT '{}'::JSONB" json:"-"` 12 | } 13 | 14 | type Order struct { 15 | gorm.Model 16 | User User 17 | Data string `sql:"type:JSONB NOT NULL DEFAULT '{}'::JSONB"` 18 | } 19 | 20 | // GORM creates tables with plural names. Use this to suppress it 21 | func (User) TableName() string { 22 | return "user" 23 | } 24 | 25 | func (Order) TableName() string { 26 | return "order" 27 | } 28 | 29 | 30 | func InitDB() (*gorm.DB, error) { 31 | var err error 32 | db, err := gorm.Open("postgres", "postgres://naren:passme123@localhost/mydb?sslmode=disable") 33 | if err != nil { 34 | return nil, err 35 | } else { 36 | /* 37 | // The below AutoMigrate is equivalent to this 38 | if !db.HasTable("user") { 39 | db.CreateTable(&User{}) 40 | } 41 | 42 | if !db.HasTable("order") { 43 | db.CreateTable(&Order{}) 44 | } 45 | */ 46 | db.AutoMigrate(&User{}, &Order{}) 47 | return db, nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Chapter07/urlshortener/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/gorilla/mux" 12 | _ "github.com/lib/pq" 13 | "github.com/narenaryan/urlshortener/models" 14 | base62 "github.com/narenaryan/urlshortener/utils" 15 | ) 16 | 17 | // DB stores the database session imformation. Needs to be initialized once 18 | type DBClient struct { 19 | db *sql.DB 20 | } 21 | 22 | // Model the record struct 23 | type Record struct { 24 | ID int `json:"id"` 25 | URL string `json:"url"` 26 | } 27 | 28 | // GetOriginalURL fetches the original URL for the given encoded(short) string 29 | func (driver *DBClient) GetOriginalURL(w http.ResponseWriter, r *http.Request) { 30 | var url string 31 | vars := mux.Vars(r) 32 | // Get ID from base62 string 33 | id := base62.ToBase10(vars["encoded_string"]) 34 | err := driver.db.QueryRow("SELECT url FROM web_url WHERE id = $1", id).Scan(&url) 35 | // Handle response details 36 | if err != nil { 37 | w.Write([]byte(err.Error())) 38 | } else { 39 | w.WriteHeader(http.StatusOK) 40 | w.Header().Set("Content-Type", "application/json") 41 | responseMap := map[string]interface{}{"url": url} 42 | response, _ := json.Marshal(responseMap) 43 | w.Write(response) 44 | } 45 | } 46 | 47 | // GenerateShortURL adds URL to DB and gives back shortened string 48 | func (driver *DBClient) GenerateShortURL(w http.ResponseWriter, r *http.Request) { 49 | var id int 50 | var record Record 51 | postBody, _ := ioutil.ReadAll(r.Body) 52 | json.Unmarshal(postBody, &record) 53 | err := driver.db.QueryRow("INSERT INTO web_url(url) VALUES($1) RETURNING id", record.URL).Scan(&id) 54 | responseMap := map[string]interface{}{"encoded_string": base62.ToBase62(id)} 55 | if err != nil { 56 | w.Write([]byte(err.Error())) 57 | } else { 58 | w.Header().Set("Content-Type", "application/json") 59 | response, _ := json.Marshal(responseMap) 60 | w.Write(response) 61 | } 62 | } 63 | 64 | func main() { 65 | db, err := models.InitDB() 66 | if err != nil { 67 | panic(err) 68 | } 69 | dbclient := &DBClient{db: db} 70 | if err != nil { 71 | panic(err) 72 | } 73 | defer db.Close() 74 | // Create a new router 75 | r := mux.NewRouter() 76 | // Attach an elegant path with handler 77 | r.HandleFunc("/v1/short/{encoded_string:[a-zA-Z0-9]*}", dbclient.GetOriginalURL).Methods("GET") 78 | r.HandleFunc("/v1/short", dbclient.GenerateShortURL).Methods("POST") 79 | srv := &http.Server{ 80 | Handler: r, 81 | Addr: "127.0.0.1:8000", 82 | // Good practice: enforce timeouts for servers you create! 83 | WriteTimeout: 15 * time.Second, 84 | ReadTimeout: 15 * time.Second, 85 | } 86 | log.Fatal(srv.ListenAndServe()) 87 | } 88 | -------------------------------------------------------------------------------- /Chapter07/urlshortener/main_test.go: -------------------------------------------------------------------------------- 1 | package main_test 2 | 3 | import ( 4 | "testing" 5 | "net/http" 6 | ) 7 | 8 | func TestGetOriginalURL(t *testing.T) { 9 | // make a dummy reques 10 | response, err := http.Get("http://localhost:8000/v1/short/1") 11 | 12 | if http.StatusOK != response.StatusCode { 13 | t.Errorf("Expected response code %d. Got %d\n", http.StatusOK, response.StatusCode) 14 | } 15 | 16 | if err != nil { 17 | t.Errorf("Encountered an error:", err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Chapter07/urlshortener/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "database/sql" 5 | _ "github.com/lib/pq" 6 | "log" 7 | ) 8 | 9 | func InitDB() (*sql.DB, error) { 10 | var err error 11 | db, err := sql.Open("postgres", "postgres://naren:passme123@localhost/mydb?sslmode=disable") 12 | if err != nil { 13 | return nil, err 14 | } else { 15 | // Create model for our URL service 16 | stmt, err := db.Prepare("CREATE TABLE WEB_URL(ID SERIAL PRIMARY KEY, URL TEXT NOT NULL);") 17 | if err != nil { 18 | log.Println(err) 19 | return nil, err 20 | } 21 | res, err := stmt.Exec() 22 | if err != nil { 23 | log.Println(err) 24 | return nil, err 25 | } 26 | return db, nil 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Chapter07/urlshortener/utils/encodeutils.go: -------------------------------------------------------------------------------- 1 | package base62 2 | 3 | import ( 4 | "math" 5 | "strings" 6 | ) 7 | 8 | const base = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 9 | const b = 62 10 | 11 | // Function encodes the given database ID to a base62 string 12 | func ToBase62(num int) string { 13 | r := num % b 14 | res := string(base[r]) 15 | div := num / b 16 | q := int(math.Floor(float64(div))) 17 | 18 | for q != 0 { 19 | r = q % b 20 | temp := q / b 21 | q = int(math.Floor(float64(temp))) 22 | res = string(base[int(r)]) + res 23 | } 24 | 25 | return string(res) 26 | } 27 | 28 | // Function decodes a given base62 string to datbase ID 29 | func ToBase10(str string) int { 30 | res := 0 31 | for _, r := range str { 32 | res = (b * res) + strings.Index(base, string(r)) 33 | } 34 | return res 35 | } 36 | -------------------------------------------------------------------------------- /Chapter08/cli/cliBasic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | func main() { 11 | // Create new app 12 | app := cli.NewApp() 13 | 14 | // add flags with three arguments 15 | app.Flags = []cli.Flag { 16 | cli.StringFlag{ 17 | Name: "name", 18 | Value: "stranger", 19 | Usage: "your wonderful name", 20 | }, 21 | cli.IntFlag{ 22 | Name: "age", 23 | Value: 0, 24 | Usage: "your graceful age", 25 | }, 26 | } 27 | // This function parses and brings data in cli.Context struct 28 | app.Action = func(c *cli.Context) error { 29 | // c.String, c.Int looks for value of given flag 30 | log.Printf("Hello %s (%d years), Welcome to the command line world", c.String("name"), c.Int("age")) 31 | return nil 32 | } 33 | 34 | // Pass os.Args to cli app to parse content 35 | app.Run(os.Args) 36 | } -------------------------------------------------------------------------------- /Chapter08/cli/storeMarks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/urfave/cli" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | app := cli.NewApp() 11 | // define flags 12 | app.Flags = []cli.Flag{ 13 | cli.StringFlag{ 14 | Name: "save", 15 | Value: "no", 16 | Usage: "Should save to database (yes/no)", 17 | }, 18 | } 19 | 20 | app.Version = "1.0" 21 | // define action 22 | app.Action = func(c *cli.Context) error { 23 | var args []string 24 | if c.NArg() > 0 { 25 | // Fetch arguments in a array 26 | args = c.Args() 27 | personName := args[0] 28 | marks := args[1:len(args)] 29 | log.Println("Person: ", personName) 30 | log.Println("marks", marks) 31 | } 32 | // check the flag value 33 | if c.String("save") == "no" { 34 | log.Println("Skipping saving to the database") 35 | } else { 36 | // Add database logic here 37 | log.Println("Saving to the database", args) 38 | } 39 | return nil 40 | } 41 | 42 | app.Run(os.Args) 43 | } 44 | -------------------------------------------------------------------------------- /Chapter08/flagExample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | ) 7 | 8 | var name = flag.String("name", "stranger", "your wonderful name") 9 | 10 | func main(){ 11 | flag.Parse() 12 | log.Printf("Hello %s, Welcome to the command line world", *name) 13 | } -------------------------------------------------------------------------------- /Chapter08/flagExampleMultiParam.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | ) 7 | 8 | var name = flag.String("name", "stranger", "your wonderful name") 9 | var age = flag.Int("age", 0, "your graceful age") 10 | 11 | func main(){ 12 | flag.Parse() 13 | log.Printf("Hello %s (%d years), Welcome to the command line world", *name, *age) 14 | } -------------------------------------------------------------------------------- /Chapter08/githubAPI/getRepos.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/levigross/grequests" 5 | "log" 6 | "os" 7 | ) 8 | 9 | var GITHUB_TOKEN = os.Getenv("GITHUB_TOKEN") 10 | var requestOptions = &grequests.RequestOptions{Auth: []string{GITHUB_TOKEN, "x-oauth-basic"}} 11 | 12 | type Repo struct { 13 | ID int `json:"id"` 14 | Name string `json:"name"` 15 | FullName string `json:"full_name"` 16 | Forks int `json:"forks"` 17 | Private bool `json:"private"` 18 | } 19 | 20 | func getStats(url string) *grequests.Response{ 21 | resp, err := grequests.Get(url, requestOptions) 22 | // You can modify the request by passing an optional RequestOptions struct 23 | if err != nil { 24 | log.Fatalln("Unable to make request: ", err) 25 | } 26 | return resp 27 | } 28 | 29 | func main() { 30 | var repos []Repo 31 | var repoUrl = "https://api.github.com/users/torvalds/repos" 32 | resp := getStats(repoUrl) 33 | resp.JSON(&repos) 34 | log.Println(repos) 35 | } 36 | -------------------------------------------------------------------------------- /Chapter08/githubAPI/gitTool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/levigross/grequests" 7 | "github.com/urfave/cli" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | ) 12 | 13 | var GITHUB_TOKEN = os.Getenv("GITHUB_TOKEN") 14 | var requestOptions = &grequests.RequestOptions{Auth: []string{GITHUB_TOKEN, "x-oauth-basic"}} 15 | 16 | // Struct for holding response of repositories fetch API 17 | type Repo struct { 18 | ID int `json:"id"` 19 | Name string `json:"name"` 20 | FullName string `json:"full_name"` 21 | Forks int `json:"forks"` 22 | Private bool `json:"private"` 23 | } 24 | 25 | // Structs for modelling JSON body in create Gist 26 | type File struct { 27 | Content string `json:"content"` 28 | } 29 | 30 | type Gist struct { 31 | Description string `json:"description"` 32 | Public bool `json:"public"` 33 | Files map[string]File `json:"files"` 34 | } 35 | 36 | // Fetches the repos for the given Github users 37 | func getStats(url string) *grequests.Response { 38 | resp, err := grequests.Get(url, requestOptions) 39 | // you can modify the request by passing an optional RequestOptions struct 40 | if err != nil { 41 | log.Fatalln("Unable to make request: ", err) 42 | } 43 | return resp 44 | } 45 | 46 | // Reads the files provided and creates Gist on github 47 | func createGist(url string, args []string) *grequests.Response { 48 | // get first teo arguments 49 | description := args[0] 50 | // remaining arguments are file names with path 51 | var fileContents = make(map[string]File) 52 | for i := 1; i < len(args); i++ { 53 | dat, err := ioutil.ReadFile(args[i]) 54 | if err != nil { 55 | log.Println("Please check the filenames. Absolute path (or) same directory are allowed") 56 | return nil 57 | } 58 | var file File 59 | file.Content = string(dat) 60 | fileContents[args[i]] = file 61 | } 62 | var gist = Gist{Description: description, Public: true, Files: fileContents} 63 | var postBody, _ = json.Marshal(gist) 64 | var requestOptions_copy = requestOptions 65 | // Add data to JSON field 66 | requestOptions_copy.JSON = string(postBody) 67 | // make a Post request to Github 68 | resp, err := grequests.Post(url, requestOptions_copy) 69 | if err != nil { 70 | log.Println("Create request failed for Github API") 71 | } 72 | return resp 73 | } 74 | 75 | func main() { 76 | app := cli.NewApp() 77 | // define command for our client 78 | app.Commands = []cli.Command{ 79 | { 80 | Name: "fetch", 81 | Aliases: []string{"f"}, 82 | Usage: "Fetch the repo details with user. [Usage]: goTool fetch user", 83 | Action: func(c *cli.Context) error { 84 | if c.NArg() > 0 { 85 | // Github API Logic 86 | var repos []Repo 87 | user := c.Args()[0] 88 | var repoUrl = fmt.Sprintf("https://api.github.com/users/%s/repos", user) 89 | resp := getStats(repoUrl) 90 | resp.JSON(&repos) 91 | log.Println(repos) 92 | } else { 93 | log.Println("Please give a username. See -h to see help") 94 | } 95 | return nil 96 | }, 97 | }, 98 | { 99 | Name: "create", 100 | Aliases: []string{"c"}, 101 | Usage: "Creates a gist from the given text. [Usage]: goTool name 'description' sample.txt", 102 | Action: func(c *cli.Context) error { 103 | if c.NArg() > 1 { 104 | // Github API Logic 105 | args := c.Args() 106 | var postUrl = "https://api.github.com/gists" 107 | resp := createGist(postUrl, args) 108 | log.Println(resp.String()) 109 | } else { 110 | log.Println("Please give sufficient arguments. See -h to see help") 111 | } 112 | return nil 113 | }, 114 | }, 115 | } 116 | 117 | app.Version = "1.0" 118 | app.Run(os.Args) 119 | } 120 | -------------------------------------------------------------------------------- /Chapter08/githubAPI/sample1.txt: -------------------------------------------------------------------------------- 1 | I am sample1 file text 2 | -------------------------------------------------------------------------------- /Chapter08/githubAPI/sample2.txt: -------------------------------------------------------------------------------- 1 | I am sample2 file text 2 | -------------------------------------------------------------------------------- /Chapter08/grequests/basicRequest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/levigross/grequests" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | resp, err := grequests.Get("http://httpbin.org/get", nil) 10 | // You can modify the request by passing an optional RequestOptions struct 11 | if err != nil { 12 | log.Fatalln("Unable to make request: ", err) 13 | } 14 | log.Println(resp.String()) 15 | } 16 | -------------------------------------------------------------------------------- /Chapter08/grequests/jsonRequest.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/levigross/grequests" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | resp, err := grequests.Get("http://httpbin.org/get", nil) 10 | // You can modify the request by passing an optional RequestOptions struct 11 | if err != nil { 12 | log.Fatalln("Unable to make request: ", err) 13 | } 14 | var returnData map[string]interface{} 15 | resp.JSON(&returnData) 16 | log.Println(returnData) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Chapter08/initFlag.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | ) 7 | 8 | var name string 9 | var age int 10 | 11 | func init() { 12 | flag.StringVar(&name, "name", "stranger", "your wonderful name") 13 | flag.IntVar(&age, "age", 0, "your graceful age") 14 | } 15 | 16 | func main(){ 17 | flag.Parse() 18 | log.Printf("Hello %s (%d years), Welcome to the command line world", name, age) 19 | } -------------------------------------------------------------------------------- /Chapter09/encryptService/helpers/endpoints.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kit/kit/endpoint" 7 | ) 8 | 9 | // EncryptService is a blueprint for our service 10 | type EncryptService interface { 11 | Encrypt(context.Context, string, string) (string, error) 12 | Decrypt(context.Context, string, string) (string, error) 13 | } 14 | 15 | // MakeEncryptEndpoint forms endpoint for request/response of encrypt function 16 | func MakeEncryptEndpoint(svc EncryptService) endpoint.Endpoint { 17 | return func(ctx context.Context, request interface{}) (interface{}, error) { 18 | req := request.(EncryptRequest) 19 | message, err := svc.Encrypt(ctx, req.Key, req.Text) 20 | if err != nil { 21 | return EncryptResponse{message, err.Error()}, nil 22 | } 23 | return EncryptResponse{message, ""}, nil 24 | } 25 | } 26 | 27 | // MakeDecryptEndpoint forms endpoint for request/response of decrypt function 28 | func MakeDecryptEndpoint(svc EncryptService) endpoint.Endpoint { 29 | return func(ctx context.Context, request interface{}) (interface{}, error) { 30 | req := request.(DecryptRequest) 31 | text, err := svc.Decrypt(ctx, req.Key, req.Message) 32 | if err != nil { 33 | return DecryptResponse{text, err.Error()}, nil 34 | } 35 | return DecryptResponse{text, ""}, nil 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter09/encryptService/helpers/implementations.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "encoding/base64" 8 | "errors" 9 | ) 10 | 11 | // EncryptServiceInstance is the implementation of interface for micro service 12 | type EncryptServiceInstance struct{} 13 | 14 | // Implements AES encryption algorithm(Rijndael Algorithm) 15 | /* Initialization vector for the AES algorithm 16 | More details visit this link https://en.wikipedia.org/wiki/Advanced_Encryption_Standard */ 17 | var initVector = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05} 18 | 19 | // Encrypt encrypts the string with given key 20 | func (EncryptServiceInstance) Encrypt(_ context.Context, key string, text string) (string, error) { 21 | block, err := aes.NewCipher([]byte(key)) 22 | if err != nil { 23 | panic(err) 24 | } 25 | plaintext := []byte(text) 26 | cfb := cipher.NewCFBEncrypter(block, initVector) 27 | ciphertext := make([]byte, len(plaintext)) 28 | cfb.XORKeyStream(ciphertext, plaintext) 29 | return base64.StdEncoding.EncodeToString(ciphertext), nil 30 | } 31 | 32 | // Decrypt decrypts the encrypted string to original 33 | func (EncryptServiceInstance) Decrypt(_ context.Context, key string, text string) (string, error) { 34 | if key == "" || text == "" { 35 | return "", errEmpty 36 | } 37 | block, err := aes.NewCipher([]byte(key)) 38 | if err != nil { 39 | panic(err) 40 | } 41 | ciphertext, _ := base64.StdEncoding.DecodeString(text) 42 | cfb := cipher.NewCFBEncrypter(block, initVector) 43 | plaintext := make([]byte, len(ciphertext)) 44 | cfb.XORKeyStream(plaintext, ciphertext) 45 | return string(plaintext), nil 46 | } 47 | 48 | var errEmpty = errors.New("Sectt Key or Text should not be empty") 49 | -------------------------------------------------------------------------------- /Chapter09/encryptService/helpers/jsonutils.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // DecodeEncryptRequest fills struct from JSON details of request 10 | func DecodeEncryptRequest(_ context.Context, r *http.Request) (interface{}, error) { 11 | var request EncryptRequest 12 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 13 | return nil, err 14 | } 15 | return request, nil 16 | } 17 | 18 | // DecodeDecryptRequest fills struct from JSON details of request 19 | func DecodeDecryptRequest(_ context.Context, r *http.Request) (interface{}, error) { 20 | var request DecryptRequest 21 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 22 | return nil, err 23 | } 24 | return request, nil 25 | } 26 | 27 | // EncodeResponse is common for both the reponses from encrypt and decrypt services 28 | func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 29 | return json.NewEncoder(w).Encode(response) 30 | } 31 | -------------------------------------------------------------------------------- /Chapter09/encryptService/helpers/models.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | // EncryptRequest strctures request coming from client 4 | type EncryptRequest struct { 5 | Text string `json:"text"` 6 | Key string `json:"key"` 7 | } 8 | 9 | // EncryptResponse strctures response going to the client 10 | type EncryptResponse struct { 11 | Message string `json:"message"` 12 | Err string `json:"error"` 13 | } 14 | 15 | // DecryptRequest strctures request coming from client 16 | type DecryptRequest struct { 17 | Message string `json:"message"` 18 | Key string `json:"key"` 19 | } 20 | 21 | // DecryptResponse strctures response going to the client 22 | type DecryptResponse struct { 23 | Text string `json:"text"` 24 | Err string `json:"error"` 25 | } 26 | -------------------------------------------------------------------------------- /Chapter09/encryptService/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | httptransport "github.com/go-kit/kit/transport/http" 8 | "github.com/narenaryan/encryptService/helpers" 9 | ) 10 | 11 | func main() { 12 | svc := helpers.EncryptServiceInstance{} 13 | encryptHandler := httptransport.NewServer(helpers.MakeEncryptEndpoint(svc), 14 | helpers.DecodeEncryptRequest, 15 | helpers.EncodeResponse) 16 | 17 | decryptHandler := httptransport.NewServer(helpers.MakeDecryptEndpoint(svc), 18 | helpers.DecodeDecryptRequest, 19 | helpers.EncodeResponse) 20 | 21 | http.Handle("/encrypt", encryptHandler) 22 | http.Handle("/decrypt", decryptHandler) 23 | log.Fatal(http.ListenAndServe(":8080", nil)) 24 | } 25 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/helpers/endpoints.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kit/kit/endpoint" 7 | ) 8 | 9 | // EncryptService is a blueprint for our service 10 | type EncryptService interface { 11 | Encrypt(context.Context, string, string) (string, error) 12 | Decrypt(context.Context, string, string) (string, error) 13 | } 14 | 15 | // MakeEncryptEndpoint forms endpoint for request/response of encrypt function 16 | func MakeEncryptEndpoint(svc EncryptService) endpoint.Endpoint { 17 | return func(ctx context.Context, request interface{}) (interface{}, error) { 18 | req := request.(EncryptRequest) 19 | message, err := svc.Encrypt(ctx, req.Key, req.Text) 20 | if err != nil { 21 | return EncryptResponse{message, err.Error()}, nil 22 | } 23 | return EncryptResponse{message, ""}, nil 24 | } 25 | } 26 | 27 | // MakeDecryptEndpoint forms endpoint for request/response of decrypt function 28 | func MakeDecryptEndpoint(svc EncryptService) endpoint.Endpoint { 29 | return func(ctx context.Context, request interface{}) (interface{}, error) { 30 | req := request.(DecryptRequest) 31 | text, err := svc.Decrypt(ctx, req.Key, req.Message) 32 | if err != nil { 33 | return DecryptResponse{text, err.Error()}, nil 34 | } 35 | return DecryptResponse{text, ""}, nil 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/helpers/implementations.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "encoding/base64" 8 | "errors" 9 | ) 10 | 11 | // EncryptServiceInstance is the implementation of interface for micro service 12 | type EncryptServiceInstance struct{} 13 | 14 | // Implements AES encryption algorithm(Rijndael Algorithm) 15 | /* Initialization vector for the AES algorithm 16 | More details visit this link https://en.wikipedia.org/wiki/Advanced_Encryption_Standard */ 17 | var initVector = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05} 18 | 19 | // Encrypt encrypts the string with given key 20 | func (EncryptServiceInstance) Encrypt(_ context.Context, key string, text string) (string, error) { 21 | block, err := aes.NewCipher([]byte(key)) 22 | if err != nil { 23 | panic(err) 24 | } 25 | plaintext := []byte(text) 26 | cfb := cipher.NewCFBEncrypter(block, initVector) 27 | ciphertext := make([]byte, len(plaintext)) 28 | cfb.XORKeyStream(ciphertext, plaintext) 29 | return base64.StdEncoding.EncodeToString(ciphertext), nil 30 | } 31 | 32 | // Decrypt decrypts the encrypted string to original 33 | func (EncryptServiceInstance) Decrypt(_ context.Context, key string, text string) (string, error) { 34 | if key == "" || text == "" { 35 | return "", errEmpty 36 | } 37 | block, err := aes.NewCipher([]byte(key)) 38 | if err != nil { 39 | panic(err) 40 | } 41 | ciphertext, _ := base64.StdEncoding.DecodeString(text) 42 | cfb := cipher.NewCFBEncrypter(block, initVector) 43 | plaintext := make([]byte, len(ciphertext)) 44 | cfb.XORKeyStream(plaintext, ciphertext) 45 | return string(plaintext), nil 46 | } 47 | 48 | var errEmpty = errors.New("Sectt Key or Text should not be empty") 49 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/helpers/instrumentation.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-kit/kit/metrics" 9 | ) 10 | 11 | // InstrumentingMiddleware is a struct representing middleware 12 | type InstrumentingMiddleware struct { 13 | RequestCount metrics.Counter 14 | RequestLatency metrics.Histogram 15 | Next EncryptService 16 | } 17 | 18 | func (mw InstrumentingMiddleware) Encrypt(ctx context.Context, key string, text string) (output string, err error) { 19 | defer func(begin time.Time) { 20 | lvs := []string{"method", "encrypt", "error", fmt.Sprint(err != nil)} 21 | mw.RequestCount.With(lvs...).Add(1) 22 | mw.RequestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) 23 | }(time.Now()) 24 | 25 | output, err = mw.Next.Encrypt(ctx, key, text) 26 | return 27 | } 28 | 29 | func (mw InstrumentingMiddleware) Decrypt(ctx context.Context, key string, text string) (output string, err error) { 30 | defer func(begin time.Time) { 31 | lvs := []string{"method", "decrypt", "error", "false"} 32 | mw.RequestCount.With(lvs...).Add(1) 33 | mw.RequestLatency.With(lvs...).Observe(time.Since(begin).Seconds()) 34 | }(time.Now()) 35 | 36 | output, err = mw.Next.Decrypt(ctx, key, text) 37 | return 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/helpers/jsonutils.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // DecodeEncryptRequest fills struct from JSON details of request 10 | func DecodeEncryptRequest(_ context.Context, r *http.Request) (interface{}, error) { 11 | var request EncryptRequest 12 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 13 | return nil, err 14 | } 15 | return request, nil 16 | } 17 | 18 | // DecodeDecryptRequest fills struct from JSON details of request 19 | func DecodeDecryptRequest(_ context.Context, r *http.Request) (interface{}, error) { 20 | var request DecryptRequest 21 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 22 | return nil, err 23 | } 24 | return request, nil 25 | } 26 | 27 | // EncodeResponse is common for both the reponses from encrypt and decrypt services 28 | func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 29 | return json.NewEncoder(w).Encode(response) 30 | } 31 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/helpers/middleware.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | log "github.com/go-kit/kit/log" 8 | ) 9 | 10 | // LoggingMiddleware wraps the logs for incoming requests 11 | type LoggingMiddleware struct { 12 | Logger log.Logger 13 | Next EncryptService 14 | } 15 | 16 | // Encrypt logs the encyption requests 17 | func (mw LoggingMiddleware) Encrypt(ctx context.Context, key string, text string) (output string, err error) { 18 | defer func(begin time.Time) { 19 | _ = mw.Logger.Log( 20 | "method", "encrypt", 21 | "key", key, 22 | "text", text, 23 | "output", output, 24 | "err", err, 25 | "took", time.Since(begin), 26 | ) 27 | }(time.Now()) 28 | 29 | output, err = mw.Next.Encrypt(ctx, key, text) 30 | return 31 | } 32 | 33 | // Decrypt logs the encyption requests 34 | func (mw LoggingMiddleware) Decrypt(ctx context.Context, key string, text string) (output string, err error) { 35 | defer func(begin time.Time) { 36 | _ = mw.Logger.Log( 37 | "method", "decrypt", 38 | "key", key, 39 | "message", text, 40 | "output", output, 41 | "err", err, 42 | "took", time.Since(begin), 43 | ) 44 | }(time.Now()) 45 | 46 | output, err = mw.Next.Decrypt(ctx, key, text) 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/helpers/models.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | // EncryptRequest strctures request coming from client 4 | type EncryptRequest struct { 5 | Text string `json:"text"` 6 | Key string `json:"key"` 7 | } 8 | 9 | // EncryptResponse strctures response going to the client 10 | type EncryptResponse struct { 11 | Message string `json:"message"` 12 | Err string `json:"error"` 13 | } 14 | 15 | // DecryptRequest strctures request coming from client 16 | type DecryptRequest struct { 17 | Message string `json:"message"` 18 | Key string `json:"key"` 19 | } 20 | 21 | // DecryptResponse strctures response going to the client 22 | type DecryptResponse struct { 23 | Text string `json:"text"` 24 | Err string `json:"error"` 25 | } 26 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithInstrumentation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | stdprometheus "github.com/prometheus/client_golang/prometheus" 9 | "github.com/prometheus/client_golang/prometheus/promhttp" 10 | 11 | kitlog "github.com/go-kit/kit/log" 12 | httptransport "github.com/go-kit/kit/transport/http" 13 | "github.com/narenaryan/encryptService/helpers" 14 | 15 | kitprometheus "github.com/go-kit/kit/metrics/prometheus" 16 | ) 17 | 18 | func main() { 19 | logger := kitlog.NewLogfmtLogger(os.Stderr) 20 | 21 | fieldKeys := []string{"method", "error"} 22 | requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ 23 | Namespace: "encryption", 24 | Subsystem: "my_service", 25 | Name: "request_count", 26 | Help: "Number of requests received.", 27 | }, fieldKeys) 28 | requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ 29 | Namespace: "encryption", 30 | Subsystem: "my_service", 31 | Name: "request_latency_microseconds", 32 | Help: "Total duration of requests in microseconds.", 33 | }, fieldKeys) 34 | 35 | var svc helpers.EncryptService 36 | svc = helpers.EncryptServiceInstance{} 37 | svc = helpers.LoggingMiddleware{Logger: logger, Next: svc} 38 | svc = helpers.InstrumentingMiddleware{RequestCount: requestCount, RequestLatency: requestLatency, Next: svc} 39 | encryptHandler := httptransport.NewServer(helpers.MakeEncryptEndpoint(svc), 40 | helpers.DecodeEncryptRequest, 41 | helpers.EncodeResponse) 42 | 43 | decryptHandler := httptransport.NewServer(helpers.MakeDecryptEndpoint(svc), 44 | helpers.DecodeDecryptRequest, 45 | helpers.EncodeResponse) 46 | 47 | http.Handle("/encrypt", encryptHandler) 48 | http.Handle("/decrypt", decryptHandler) 49 | http.Handle("/metrics", promhttp.Handler()) 50 | log.Fatal(http.ListenAndServe(":8080", nil)) 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithLogging/helpers/endpoints.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-kit/kit/endpoint" 7 | ) 8 | 9 | // EncryptService is a blueprint for our service 10 | type EncryptService interface { 11 | Encrypt(context.Context, string, string) (string, error) 12 | Decrypt(context.Context, string, string) (string, error) 13 | } 14 | 15 | // MakeEncryptEndpoint forms endpoint for request/response of encrypt function 16 | func MakeEncryptEndpoint(svc EncryptService) endpoint.Endpoint { 17 | return func(ctx context.Context, request interface{}) (interface{}, error) { 18 | req := request.(EncryptRequest) 19 | message, err := svc.Encrypt(ctx, req.Key, req.Text) 20 | if err != nil { 21 | return EncryptResponse{message, err.Error()}, nil 22 | } 23 | return EncryptResponse{message, ""}, nil 24 | } 25 | } 26 | 27 | // MakeDecryptEndpoint forms endpoint for request/response of decrypt function 28 | func MakeDecryptEndpoint(svc EncryptService) endpoint.Endpoint { 29 | return func(ctx context.Context, request interface{}) (interface{}, error) { 30 | req := request.(DecryptRequest) 31 | text, err := svc.Decrypt(ctx, req.Key, req.Message) 32 | if err != nil { 33 | return DecryptResponse{text, err.Error()}, nil 34 | } 35 | return DecryptResponse{text, ""}, nil 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithLogging/helpers/implementations.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "encoding/base64" 8 | "errors" 9 | ) 10 | 11 | // EncryptServiceInstance is the implementation of interface for micro service 12 | type EncryptServiceInstance struct{} 13 | 14 | // Implements AES encryption algorithm(Rijndael Algorithm) 15 | /* Initialization vector for the AES algorithm 16 | More details visit this link https://en.wikipedia.org/wiki/Advanced_Encryption_Standard */ 17 | var initVector = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05} 18 | 19 | // Encrypt encrypts the string with given key 20 | func (EncryptServiceInstance) Encrypt(_ context.Context, key string, text string) (string, error) { 21 | block, err := aes.NewCipher([]byte(key)) 22 | if err != nil { 23 | panic(err) 24 | } 25 | plaintext := []byte(text) 26 | cfb := cipher.NewCFBEncrypter(block, initVector) 27 | ciphertext := make([]byte, len(plaintext)) 28 | cfb.XORKeyStream(ciphertext, plaintext) 29 | return base64.StdEncoding.EncodeToString(ciphertext), nil 30 | } 31 | 32 | // Decrypt decrypts the encrypted string to original 33 | func (EncryptServiceInstance) Decrypt(_ context.Context, key string, text string) (string, error) { 34 | if key == "" || text == "" { 35 | return "", errEmpty 36 | } 37 | block, err := aes.NewCipher([]byte(key)) 38 | if err != nil { 39 | panic(err) 40 | } 41 | ciphertext, _ := base64.StdEncoding.DecodeString(text) 42 | cfb := cipher.NewCFBEncrypter(block, initVector) 43 | plaintext := make([]byte, len(ciphertext)) 44 | cfb.XORKeyStream(plaintext, ciphertext) 45 | return string(plaintext), nil 46 | } 47 | 48 | var errEmpty = errors.New("Sectt Key or Text should not be empty") 49 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithLogging/helpers/jsonutils.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | ) 8 | 9 | // DecodeEncryptRequest fills struct from JSON details of request 10 | func DecodeEncryptRequest(_ context.Context, r *http.Request) (interface{}, error) { 11 | var request EncryptRequest 12 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 13 | return nil, err 14 | } 15 | return request, nil 16 | } 17 | 18 | // DecodeDecryptRequest fills struct from JSON details of request 19 | func DecodeDecryptRequest(_ context.Context, r *http.Request) (interface{}, error) { 20 | var request DecryptRequest 21 | if err := json.NewDecoder(r.Body).Decode(&request); err != nil { 22 | return nil, err 23 | } 24 | return request, nil 25 | } 26 | 27 | // EncodeResponse is common for both the reponses from encrypt and decrypt services 28 | func EncodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { 29 | return json.NewEncoder(w).Encode(response) 30 | } 31 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithLogging/helpers/middleware.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | log "github.com/go-kit/kit/log" 8 | ) 9 | 10 | // LoggingMiddleware wraps the logs for incoming requests 11 | type LoggingMiddleware struct { 12 | Logger log.Logger 13 | Next EncryptService 14 | } 15 | 16 | // Encrypt logs the encyption requests 17 | func (mw LoggingMiddleware) Encrypt(ctx context.Context, key string, text string) (output string, err error) { 18 | defer func(begin time.Time) { 19 | _ = mw.Logger.Log( 20 | "method", "encrypt", 21 | "key", key, 22 | "text", text, 23 | "output", output, 24 | "err", err, 25 | "took", time.Since(begin), 26 | ) 27 | }(time.Now()) 28 | 29 | output, err = mw.Next.Encrypt(ctx, key, text) 30 | return 31 | } 32 | 33 | // Decrypt logs the encyption requests 34 | func (mw LoggingMiddleware) Decrypt(ctx context.Context, key string, text string) (output string, err error) { 35 | defer func(begin time.Time) { 36 | _ = mw.Logger.Log( 37 | "method", "decrypt", 38 | "key", key, 39 | "message", text, 40 | "output", output, 41 | "err", err, 42 | "took", time.Since(begin), 43 | ) 44 | }(time.Now()) 45 | 46 | output, err = mw.Next.Decrypt(ctx, key, text) 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithLogging/helpers/models.go: -------------------------------------------------------------------------------- 1 | package helpers 2 | 3 | // EncryptRequest strctures request coming from client 4 | type EncryptRequest struct { 5 | Text string `json:"text"` 6 | Key string `json:"key"` 7 | } 8 | 9 | // EncryptResponse strctures response going to the client 10 | type EncryptResponse struct { 11 | Message string `json:"message"` 12 | Err string `json:"error"` 13 | } 14 | 15 | // DecryptRequest strctures request coming from client 16 | type DecryptRequest struct { 17 | Message string `json:"message"` 18 | Key string `json:"key"` 19 | } 20 | 21 | // DecryptResponse strctures response going to the client 22 | type DecryptResponse struct { 23 | Text string `json:"text"` 24 | Err string `json:"error"` 25 | } 26 | -------------------------------------------------------------------------------- /Chapter09/encryptServiceWithLogging/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | kitlog "github.com/go-kit/kit/log" 9 | httptransport "github.com/go-kit/kit/transport/http" 10 | "github.com/narenaryan/encryptService/helpers" 11 | ) 12 | 13 | func main() { 14 | logger := kitlog.NewLogfmtLogger(os.Stderr) 15 | var svc helpers.EncryptService 16 | svc = helpers.EncryptServiceInstance{} 17 | svc = helpers.LoggingMiddleware{Logger: logger, Next: svc} 18 | encryptHandler := httptransport.NewServer(helpers.MakeEncryptEndpoint(svc), 19 | helpers.DecodeEncryptRequest, 20 | helpers.EncodeResponse) 21 | 22 | decryptHandler := httptransport.NewServer(helpers.MakeDecryptEndpoint(svc), 23 | helpers.DecodeDecryptRequest, 24 | helpers.EncodeResponse) 25 | 26 | http.Handle("/encrypt", encryptHandler) 27 | http.Handle("/decrypt", decryptHandler) 28 | log.Fatal(http.ListenAndServe(":8080", nil)) 29 | } 30 | -------------------------------------------------------------------------------- /Chapter09/encryptString/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/narenaryan/encryptString/utils" 7 | ) 8 | 9 | // AES keys should be of length 16, 24, 32 10 | func main() { 11 | key := "111023043350789514532147" 12 | message := "I am A Message" 13 | log.Println("Original message: ", message) 14 | encryptedString := utils.EncryptString(key, message) 15 | log.Println("Encrypted message: ", encryptedString) 16 | decryptedString := utils.DecryptString(key, encryptedString) 17 | log.Println("Decrypted message: ", decryptedString) 18 | } 19 | -------------------------------------------------------------------------------- /Chapter09/encryptString/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "encoding/base64" 7 | ) 8 | 9 | // Implements AES encryption algorithm(Rijndael Algorithm) 10 | 11 | /* Initialization vector for the AES algorithm 12 | More details visit this link https://en.wikipedia.org/wiki/Advanced_Encryption_Standard */ 13 | var initVector = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05} 14 | 15 | // EncryptString encrypts the string with given key 16 | func EncryptString(key, text string) string { 17 | block, err := aes.NewCipher([]byte(key)) 18 | if err != nil { 19 | panic(err) 20 | } 21 | plaintext := []byte(text) 22 | cfb := cipher.NewCFBEncrypter(block, initVector) 23 | ciphertext := make([]byte, len(plaintext)) 24 | cfb.XORKeyStream(ciphertext, plaintext) 25 | return base64.StdEncoding.EncodeToString(ciphertext) 26 | } 27 | 28 | // DecryptString decrypts the encrypted string to original 29 | func DecryptString(key, text string) string { 30 | block, err := aes.NewCipher([]byte(key)) 31 | if err != nil { 32 | panic(err) 33 | } 34 | ciphertext, _ := base64.StdEncoding.DecodeString(text) 35 | cfb := cipher.NewCFBEncrypter(block, initVector) 36 | plaintext := make([]byte, len(ciphertext)) 37 | cfb.XORKeyStream(plaintext, ciphertext) 38 | return string(plaintext) 39 | } 40 | -------------------------------------------------------------------------------- /Chapter10/basicServer/app.log: -------------------------------------------------------------------------------- 1 | 2017/08/29 20:21:00 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36" 2 | 2017/08/29 22:12:26 "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36" 3 | -------------------------------------------------------------------------------- /Chapter10/basicServer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "encoding/json" 6 | "log" 7 | "net/http" 8 | "os" 9 | "time" 10 | ) 11 | 12 | type Book struct { 13 | ID int 14 | ISBN string 15 | Author string 16 | PublishedYear string 17 | } 18 | 19 | func main() { 20 | f, err := os.OpenFile("app.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 21 | if err != nil { 22 | fmt.Printf("error opening file: %v", err) 23 | } 24 | defer f.Close() 25 | log.SetOutput(f) 26 | 27 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 28 | log.Printf("%q", r.UserAgent()) 29 | book := Book{ID: 123, ISBN: "0-201-03801-3", Author: "Donald Knuth", PublishedYear: "1968"} 30 | jsonData, _ := json.Marshal(book) 31 | w.Header().Set("Content-Type", "application/json") 32 | w.Write(jsonData) 33 | }) 34 | s := &http.Server{ 35 | Addr: ":8000", 36 | ReadTimeout: 10 * time.Second, 37 | WriteTimeout: 10 * time.Second, 38 | MaxHeaderBytes: 1 << 20, 39 | } 40 | 41 | log.Fatal(s.ListenAndServe()) 42 | } -------------------------------------------------------------------------------- /Chapter10/myproject.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | logfile = /tmp/supervisord.log 3 | [program:myserver] 4 | command=/home/naren/golab/bin/myserver 5 | autostart=true 6 | autorestart=true 7 | redirect_stderr=true -------------------------------------------------------------------------------- /Chapter10/securenginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 192.168.1.23:8080; 3 | root /usr/share/nginx/html; 4 | 5 | location /status { 6 | status; 7 | satisfy all; 8 | 9 | deny 192.168.1.2; 10 | allow 192.168.1.1/24; 11 | allow 127.0.0.1; 12 | deny all; 13 | 14 | auth_basic “Administrator’s area; 15 | auth_basic_user_file /etc/apache2/.htpasswd; 16 | } 17 | 18 | location = /status.html { 19 | } 20 | } -------------------------------------------------------------------------------- /Chapter11/Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from a Debian image with the latest version of Go installed 2 | # and a workspace (GOPATH) configured at /go. 3 | FROM golang 4 | 5 | # Copy the local package files to the container's workspace. 6 | ADD kongExample /go/src/github.com/narenaryan/kongExample 7 | 8 | # Install Gorilla Mux & other dependencies 9 | RUN go get github.com/gorilla/mux 10 | 11 | # Install our package 12 | RUN go install github.com/narenaryan/kongExample 13 | 14 | # Run the outyet command by default when the container starts. 15 | ENTRYPOINT /go/bin/kongExample 16 | -------------------------------------------------------------------------------- /Chapter11/Kong.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": [], 3 | "info": { 4 | "name": "Kong", 5 | "_postman_id": "fcf82a68-2043-5ed6-5229-62fe3355480f", 6 | "description": "Set of API for kong", 7 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" 8 | }, 9 | "item": [ 10 | { 11 | "name": "Kong GET apis", 12 | "request": { 13 | "url": "localhost:8001/apis", 14 | "method": "GET", 15 | "header": [], 16 | "body": {}, 17 | "description": "" 18 | }, 19 | "response": [] 20 | }, 21 | { 22 | "name": "Kong Add API", 23 | "request": { 24 | "url": "localhost:8001/apis", 25 | "method": "POST", 26 | "header": [ 27 | { 28 | "key": "Content-Type", 29 | "value": "application/json", 30 | "description": "" 31 | } 32 | ], 33 | "body": { 34 | "mode": "raw", 35 | "raw": "{\n\t\"name\": \"myapi\",\n\t\"hosts\": \"server1\",\n\t\"upstream_url\": \"http://go-server:3000\",\n\t\"uris\":[\"/api/v1\"],\n\t\"strip_uri\": true,\n\t\"preserve_host\": false\n}" 36 | }, 37 | "description": "" 38 | }, 39 | "response": [] 40 | }, 41 | { 42 | "name": "Kong Delete API", 43 | "request": { 44 | "url": "localhost:8001/apis/myapi", 45 | "method": "DELETE", 46 | "header": [], 47 | "body": {}, 48 | "description": "" 49 | }, 50 | "response": [] 51 | }, 52 | { 53 | "name": "Kong status", 54 | "request": { 55 | "url": "localhost:8001/status", 56 | "method": "GET", 57 | "header": [], 58 | "body": {}, 59 | "description": "" 60 | }, 61 | "response": [] 62 | }, 63 | { 64 | "name": "Get healthcheck", 65 | "request": { 66 | "url": "localhost:8000/api/v1/healthcheck", 67 | "method": "GET", 68 | "header": [ 69 | { 70 | "key": "Host", 71 | "value": "server1", 72 | "description": "" 73 | } 74 | ], 75 | "body": {}, 76 | "description": "" 77 | }, 78 | "response": [] 79 | }, 80 | { 81 | "name": "Enable file logging plugin", 82 | "request": { 83 | "url": "localhost:8001/apis/myapi/plugins", 84 | "method": "POST", 85 | "header": [ 86 | { 87 | "key": "Content-Type", 88 | "value": "application/json", 89 | "description": "" 90 | } 91 | ], 92 | "body": { 93 | "mode": "raw", 94 | "raw": "{\n\t\"name\": \"file-log\",\n\t\"config.path\":\"/tmp/file.log\"\n\n}" 95 | }, 96 | "description": "" 97 | }, 98 | "response": [] 99 | }, 100 | { 101 | "name": "Kong enable key auth", 102 | "request": { 103 | "url": "http://localhost:8001/apis/myapi/plugins", 104 | "method": "POST", 105 | "header": [ 106 | { 107 | "key": "Content-Type", 108 | "value": "application/json", 109 | "description": "" 110 | } 111 | ], 112 | "body": { 113 | "mode": "raw", 114 | "raw": "{\n\t\"name\" : \"key-auth\",\n\t\"config.hide_credentials\" : true\n}" 115 | }, 116 | "description": "" 117 | }, 118 | "response": [] 119 | }, 120 | { 121 | "name": "Kong create consumer", 122 | "request": { 123 | "url": "http://localhost:8001/consumers/", 124 | "method": "POST", 125 | "header": [ 126 | { 127 | "key": "Content-Type", 128 | "value": "application/json", 129 | "description": "" 130 | } 131 | ], 132 | "body": { 133 | "mode": "raw", 134 | "raw": "{\n\t\"username\": \"johnd\"\n}" 135 | }, 136 | "description": "Generated from a curl request: \ncurl -X POST http://kong:8001/consumers/ \\\n --data \\\"username=\\\" \\\n --data \\\"custom_id=\\\"" 137 | }, 138 | "response": [] 139 | }, 140 | { 141 | "name": "Kong Create API Key for Consumer", 142 | "request": { 143 | "url": "http://localhost:8001/consumers/johnd/key-auth", 144 | "method": "POST", 145 | "header": [ 146 | { 147 | "key": "Content-Type", 148 | "value": "application/x-www-form-urlencoded", 149 | "description": "" 150 | } 151 | ], 152 | "body": { 153 | "mode": "raw", 154 | "raw": "" 155 | }, 156 | "description": "" 157 | }, 158 | "response": [] 159 | }, 160 | { 161 | "name": "Kong rate limit consumer", 162 | "request": { 163 | "url": "http://localhost:8001/apis/myapi/plugins", 164 | "method": "POST", 165 | "header": [ 166 | { 167 | "key": "Content-Type", 168 | "value": "application/json", 169 | "description": "" 170 | } 171 | ], 172 | "body": { 173 | "mode": "raw", 174 | "raw": "{\n\t\"name\" : \"rate-limiting\",\n\t\"config.hour\" : \"5000\",\n\t\"consumer_id\" : \"df024acb-5cbd-4e4d-b3ed-751287eafd36\"\n}" 175 | }, 176 | "description": "" 177 | }, 178 | "response": [] 179 | } 180 | ] 181 | } -------------------------------------------------------------------------------- /Chapter11/kongExample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gorilla/mux" 10 | ) 11 | 12 | func HealthcheckHandler(w http.ResponseWriter, r *http.Request) { 13 | w.WriteHeader(http.StatusOK) 14 | fmt.Fprintf(w, time.Now().String()) 15 | } 16 | func main() { 17 | // Create a new router 18 | r := mux.NewRouter() 19 | // Attach an elegant path with handler 20 | r.HandleFunc("/healthcheck", HealthcheckHandler) 21 | srv := &http.Server{ 22 | Handler: r, 23 | Addr: "0.0.0.0:3000", 24 | // Good practice: enforce timeouts for servers you create! 25 | WriteTimeout: 15 * time.Second, 26 | ReadTimeout: 15 * time.Second, 27 | } 28 | log.Fatal(srv.ListenAndServe()) 29 | } 30 | -------------------------------------------------------------------------------- /Chapter11/kong_install_docker.txt: -------------------------------------------------------------------------------- 1 | docker run -d --name kong-database \ 2 | -p 5432:5432 \ 3 | -e "POSTGRES_USER=kong" \ 4 | -e "POSTGRES_DB=kong" \ 5 | postgres:9.4 6 | 7 | 8 | docker run --rm \ 9 | --link kong-database:kong-database \ 10 | -e "KONG_DATABASE=postgres" \ 11 | -e "KONG_PG_HOST=kong-database" \ 12 | kong:latest kong migrations up 13 | 14 | 15 | docker run -d --name kong \ 16 | --link kong-database:kong-database \ 17 | --link go-server:go-server \ 18 | -e "KONG_DATABASE=postgres" \ 19 | -e "KONG_PG_HOST=kong-database" \ 20 | -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \ 21 | -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \ 22 | -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \ 23 | -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \ 24 | -p 8000:8000 \ 25 | -p 8443:8443 \ 26 | -p 8001:8001 \ 27 | -p 8444:8444 \ 28 | kong:latest -------------------------------------------------------------------------------- /Chapter12/jwtAuth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | jwt "github.com/dgrijalva/jwt-go" 12 | "github.com/dgrijalva/jwt-go/request" 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | var secretKey = []byte(os.Getenv("SESSION_SECRET")) 17 | var users = map[string]string{"naren": "passme", "admin": "password"} 18 | 19 | // Response is a representation of JSON response for JWT 20 | type Response struct { 21 | Token string `json:"token"` 22 | Status string `json:"status"` 23 | } 24 | 25 | // HealthcheckHandler returns the date and time 26 | func HealthcheckHandler(w http.ResponseWriter, r *http.Request) { 27 | tokenString, err := request.HeaderExtractor{"access_token"}.ExtractToken(r) 28 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 29 | // Don't forget to validate the alg is what you expect: 30 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 31 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 32 | } 33 | 34 | // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") 35 | return secretKey, nil 36 | }) 37 | if err != nil { 38 | w.WriteHeader(http.StatusForbidden) 39 | w.Write([]byte("Access Denied; Please check the access token")) 40 | return 41 | } 42 | if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { 43 | // If token is valid 44 | response := make(map[string]string) 45 | // response["user"] = claims["username"] 46 | response["time"] = time.Now().String() 47 | response["user"] = claims["username"].(string) 48 | responseJSON, _ := json.Marshal(response) 49 | w.Write(responseJSON) 50 | } else { 51 | w.WriteHeader(http.StatusForbidden) 52 | w.Write([]byte(err.Error())) 53 | } 54 | } 55 | 56 | // LoginHandler validates the user credentials 57 | func getTokenHandler(w http.ResponseWriter, r *http.Request) { 58 | err := r.ParseForm() 59 | if err != nil { 60 | http.Error(w, "Please pass the data as URL form encoded", http.StatusBadRequest) 61 | return 62 | } 63 | username := r.PostForm.Get("username") 64 | password := r.PostForm.Get("password") 65 | if originalPassword, ok := users[username]; ok { 66 | if password == originalPassword { 67 | // Create a claims map 68 | claims := jwt.MapClaims{ 69 | "username": username, 70 | "ExpiresAt": 15000, 71 | "IssuedAt": time.Now().Unix(), 72 | } 73 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 74 | tokenString, err := token.SignedString(secretKey) 75 | if err != nil { 76 | w.WriteHeader(http.StatusBadGateway) 77 | w.Write([]byte(err.Error())) 78 | } 79 | response := Response{Token: tokenString, Status: "success"} 80 | responseJSON, _ := json.Marshal(response) 81 | w.WriteHeader(http.StatusOK) 82 | w.Header().Set("Content-Type", "application/json") 83 | w.Write(responseJSON) 84 | 85 | } else { 86 | http.Error(w, "Invalid Credentials", http.StatusUnauthorized) 87 | return 88 | } 89 | } else { 90 | http.Error(w, "User is not found", http.StatusNotFound) 91 | return 92 | } 93 | } 94 | 95 | func main() { 96 | r := mux.NewRouter() 97 | r.HandleFunc("/getToken", getTokenHandler) 98 | r.HandleFunc("/healthcheck", HealthcheckHandler) 99 | http.Handle("/", r) 100 | srv := &http.Server{ 101 | Handler: r, 102 | Addr: "127.0.0.1:8000", 103 | // Good practice: enforce timeouts for servers you create! 104 | WriteTimeout: 15 * time.Second, 105 | ReadTimeout: 15 * time.Second, 106 | } 107 | log.Fatal(srv.ListenAndServe()) 108 | } 109 | -------------------------------------------------------------------------------- /Chapter12/simpleAuth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/gorilla/mux" 10 | "github.com/gorilla/sessions" 11 | ) 12 | 13 | var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET"))) 14 | var users = map[string]string{"naren": "passme", "admin": "password"} 15 | 16 | // HealthcheckHandler returns the date and time 17 | func HealthcheckHandler(w http.ResponseWriter, r *http.Request) { 18 | session, _ := store.Get(r, "session.id") 19 | if (session.Values["authenticated"] != nil) && session.Values["authenticated"] != false { 20 | w.Write([]byte(time.Now().String())) 21 | } else { 22 | http.Error(w, "Forbidden", http.StatusForbidden) 23 | } 24 | } 25 | 26 | // LoginHandler validates the user credentials 27 | func LoginHandler(w http.ResponseWriter, r *http.Request) { 28 | session, _ := store.Get(r, "session.id") 29 | err := r.ParseForm() 30 | if err != nil { 31 | http.Error(w, "Please pass the data as URL form encoded", http.StatusBadRequest) 32 | return 33 | } 34 | username := r.PostForm.Get("username") 35 | password := r.PostForm.Get("password") 36 | if originalPassword, ok := users[username]; ok { 37 | if password == originalPassword { 38 | session.Values["authenticated"] = true 39 | session.Save(r, w) 40 | } else { 41 | http.Error(w, "Invalid Credentials", http.StatusUnauthorized) 42 | return 43 | } 44 | } else { 45 | http.Error(w, "User is not found", http.StatusNotFound) 46 | return 47 | } 48 | w.Write([]byte("Logged In successfully")) 49 | 50 | } 51 | 52 | // LogoutHandler removes the session 53 | func LogoutHandler(w http.ResponseWriter, r *http.Request) { 54 | session, _ := store.Get(r, "session.id") 55 | session.Values["authenticated"] = false 56 | session.Save(r, w) 57 | w.Write([]byte("")) 58 | } 59 | 60 | func main() { 61 | r := mux.NewRouter() 62 | r.HandleFunc("/login", LoginHandler) 63 | r.HandleFunc("/healthcheck", HealthcheckHandler) 64 | r.HandleFunc("/logout", LogoutHandler) 65 | http.Handle("/", r) 66 | srv := &http.Server{ 67 | Handler: r, 68 | Addr: "127.0.0.1:8000", 69 | // Good practice: enforce timeouts for servers you create! 70 | WriteTimeout: 15 * time.Second, 71 | ReadTimeout: 15 * time.Second, 72 | } 73 | log.Fatal(srv.ListenAndServe()) 74 | } 75 | -------------------------------------------------------------------------------- /Chapter12/simpleAuthWithRedis/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "time" 8 | 9 | "github.com/gorilla/mux" 10 | redistore "gopkg.in/boj/redistore.v1" 11 | ) 12 | 13 | var store, err = redistore.NewRediStore(10, "tcp", ":6379", "", []byte(os.Getenv("SESSION_SECRET"))) 14 | var users = map[string]string{"naren": "passme", "admin": "password"} 15 | 16 | // HealthcheckHandler returns the date and time 17 | func HealthcheckHandler(w http.ResponseWriter, r *http.Request) { 18 | session, _ := store.Get(r, "session.id") 19 | if (session.Values["authenticated"] != nil) && session.Values["authenticated"] != false { 20 | w.Write([]byte(time.Now().String())) 21 | } else { 22 | http.Error(w, "Forbidden", http.StatusForbidden) 23 | } 24 | } 25 | 26 | // LoginHandler validates the user credentials 27 | func LoginHandler(w http.ResponseWriter, r *http.Request) { 28 | session, _ := store.Get(r, "session.id") 29 | err := r.ParseForm() 30 | if err != nil { 31 | http.Error(w, "Please pass the data as URL form encoded", http.StatusBadRequest) 32 | return 33 | } 34 | username := r.PostForm.Get("username") 35 | password := r.PostForm.Get("password") 36 | if originalPassword, ok := users[username]; ok { 37 | if password == originalPassword { 38 | session.Values["authenticated"] = true 39 | session.Save(r, w) 40 | } else { 41 | http.Error(w, "Invalid Credentials", http.StatusUnauthorized) 42 | return 43 | } 44 | } else { 45 | http.Error(w, "User is not found", http.StatusNotFound) 46 | return 47 | } 48 | w.Write([]byte("Logged In successfully")) 49 | 50 | } 51 | 52 | // LogoutHandler removes the session 53 | func LogoutHandler(w http.ResponseWriter, r *http.Request) { 54 | session, _ := store.Get(r, "session.id") 55 | session.Options.MaxAge = -1 56 | session.Save(r, w) 57 | w.Write([]byte("")) 58 | } 59 | 60 | func main() { 61 | defer store.Close() 62 | r := mux.NewRouter() 63 | r.HandleFunc("/login", LoginHandler) 64 | r.HandleFunc("/healthcheck", HealthcheckHandler) 65 | r.HandleFunc("/logout", LogoutHandler) 66 | http.Handle("/", r) 67 | srv := &http.Server{ 68 | Handler: r, 69 | Addr: "127.0.0.1:8000", 70 | // Good practice: enforce timeouts for servers you create! 71 | WriteTimeout: 15 * time.Second, 72 | ReadTimeout: 15 * time.Second, 73 | } 74 | log.Fatal(srv.ListenAndServe()) 75 | } 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Building RESTful Web services with Go 5 | This is the code repository for [Building RESTful Web services with Go](https://www.packtpub.com/application-development/building-restful-web-services-go?utm_source=github&utm_medium=repository&utm_campaign=9781788294287), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish. 6 | ## About the Book 7 | Initially, SOAP-based web services became more popular with XML. Then, since 2012, 8 | REST picked up the pace and gulped SOAP in whole. The rise of a new generation of web 9 | languages, such as Python, JavaScript (Node.js), and Go, showed a different approach to 10 | web development compared to the traditional ones, such as ASP.NET and Spring. Since this 11 | decade, Go has become more and more popular due to its speed and intuitiveness. Less 12 | verbose code, strict type checking, and support for concurrency make Go a better choice for 13 | writing any web backend. Some of the best tools, such as Docker and Kubernetes, are 14 | written in Go. Google uses Go a lot in its daily activities. You can see a list of Go-using 15 | companies at https://github.com/golang/go/wiki/GoUsers. 16 | ## Instructions and Navigation 17 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. 18 | 19 | 20 | 21 | The code will look like the following: 22 | ``` 23 | { 24 | "ID": 1, 25 | "DriverName": "Menaka", 26 | "OperatingStatus": true 27 | } 28 | ``` 29 | 30 | For this book, you need a laptop/PC with Linux (Ubuntu 16.04), macOS X, or Windows 31 | installed. We will use Go 1.8+ as the version of our compiler and install many third-party 32 | packages, so a working internet connection is required. 33 | We will also use Docker in the final chapters to explain concepts of API Gateway. Docker 34 | V17.0+ is recommended. If Windows users have problems with the native Go installation 35 | for any examples, use Docker for Windows and run Ubuntu container, which gives more 36 | flexibility; refer to https://www.docker.com/docker-windows for more details. 37 | Before diving into the book, refresh your language basics at https://tour.golang.org/welcome/1. 38 | Even though these are the basic requirements, we will guide you through the installations 39 | wherever required. 40 | 41 | ## Related Products 42 | * [Go: Design Patterns for Real-World Projects](https://www.packtpub.com/application-development/go-design-patterns-real-world-projects?utm_source=github&utm_medium=repository&utm_campaign=9781788390552) 43 | 44 | * [Building RESTful Web Services with Spring 5 - Second Edition](https://www.packtpub.com/application-development/building-restful-web-services-spring-5-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788475891) 45 | 46 | * [Mastering Go](https://www.packtpub.com/networking-and-servers/mastering-go?utm_source=github&utm_medium=repository&utm_campaign=9781788626545) 47 | ### Download a free PDF 48 | 49 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
50 |

https://packt.link/free-ebook/9781788294287

--------------------------------------------------------------------------------