From 0504ec67efbf88500e55f30ee7eb35ffafd36ba3 Mon Sep 17 00:00:00 2001 From: Francesc Campoy Date: Thu, 23 Feb 2017 11:32:38 +0530 Subject: [PATCH 1/5] add error check in sample code Change-Id: I31cd7b695b9d7f58edb5fa512af2c64b2fc2eebf --- section02/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/section02/README.md b/section02/README.md index 9b0be3a..ee951f0 100644 --- a/section02/README.md +++ b/section02/README.md @@ -236,7 +236,9 @@ func main() { // handle all requests with the Gorilla router. http.Handle("/", r) - http.ListenAndServe("127.0.0.1:8080", nil) + if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil { + log.Fatal(err) + } } ``` From 985c038ad05b7107758c18ad345ec6c273e7cf13 Mon Sep 17 00:00:00 2001 From: Francesc Campoy Date: Sat, 25 Feb 2017 10:37:57 +0530 Subject: [PATCH 2/5] use embedmd for all code snippets Change-Id: I4606787660195ec9785de3be0f77600293c4bbb8 --- section00/README.md | 19 ++-- section01/README.md | 21 ++-- section02/README.md | 15 ++- section02/examples/gorilla.go | 52 ++++++++++ section02/examples/step1.go | 23 +++++ section02/examples/step2.go | 27 +++++ section02/{main.go => examples/step3.go} | 0 section03/README.md | 43 +++++--- section03/examples/text_handler.go | 39 ++++++++ section04/README.md | 27 +++-- section04/app/app.go | 2 +- section04/app/app.yaml | 8 +- section04/examples/hello.go | 27 +++++ section05/README.md | 38 +++++--- section05/mixed_content/hello.html | 1 - section06/README.md | 65 +++---------- section06/examples/app.go | 40 ++++++++ section07/README.md | 96 ++++++++++-------- section07/examples/app.go | 56 +++++++++++ section08/README.md | 14 ++- section09/README.md | 119 +++++++++++++++-------- section09/codec/app.go | 11 ++- 22 files changed, 540 insertions(+), 203 deletions(-) create mode 100644 section02/examples/gorilla.go create mode 100644 section02/examples/step1.go create mode 100644 section02/examples/step2.go rename section02/{main.go => examples/step3.go} (100%) create mode 100644 section03/examples/text_handler.go create mode 100644 section04/examples/hello.go diff --git a/section00/README.md b/section00/README.md index 917830b..3588ce5 100644 --- a/section00/README.md +++ b/section00/README.md @@ -2,14 +2,15 @@ Before we start writing web servers let's analyze a simple Go program. +[embedmd]:# (hello/main.go /package main/ $) ```go package main import "fmt" -func main() { - fmt.Println( "hello, world!" ); - } +func main() { + fmt.Println("hello, world!") +} ``` You should be able to understand every line of this program. @@ -42,18 +43,18 @@ But this doesn't mean you need to write them yourself! Try deleting the line `import "fmt"` from `main.go` and run it, you should see an error: -``` - $ go run main.go - # command-line-arguments - ./main.go:5: undefined: fmt in fmt.Println +```bash +$ go run main.go +# command-line-arguments +./main.go:5: undefined: fmt in fmt.Println ``` You can fix this error by manually adding the missing import statements or using `goimports`. If you don't have `goimports` installed in your machine you can easily install it by running: -``` - $ go get golang.org/x/tools/cmd/goimports +```bash +$ go get golang.org/x/tools/cmd/goimports ``` This will install the `goimports` binary in `$GOAPTH/bin`. diff --git a/section01/README.md b/section01/README.md index d653499..c750648 100644 --- a/section01/README.md +++ b/section01/README.md @@ -16,21 +16,23 @@ But before that it is important to realize that there's helper functions that wi One of those helper functions is [`Get`](https://golang.org/pkg/net/http#Get). +[embedmd]:# (examples/get.go /package main/ $) ```go package main import ( - "fmt" - "log" - "net/http" + "fmt" + "log" + "net/http" ) func main() { - res, err := http.Get("https://golang.org") - if err != nil { - log.Fatal(err) - } - fmt.Println(res.Status) + // try changing the value of this url + res, err := http.Get("https://golang.org") + if err != nil { + log.Fatal(err) + } + fmt.Println(res.Status) } ``` [source code](examples/get.go) @@ -84,6 +86,7 @@ For instance, we can create the equivalent request to the original `get.go` prog The `Client` type exposes the `Do` method that send the given `Request` and returns a `Response` and an `error`. +[embedmd]:# (examples/do-get.go /package main/ $) ```go package main @@ -121,7 +124,7 @@ Replace `YourName` with your name, or something unique that no one else might be This will save whatever string you send in the Body of the request so you can retrieve it later with: ``` - $ curl https://http-methods.appspot.com/YourName/Message +$ curl https://http-methods.appspot.com/YourName/Message ``` ## Parameters: queries and forms diff --git a/section02/README.md b/section02/README.md index ee951f0..f079a5e 100644 --- a/section02/README.md +++ b/section02/README.md @@ -20,6 +20,7 @@ method which makes it satisfy the `io.Writer` interface. Let's see a very simple HTTP handler that simply writes `"Hello, web"` to the output: +[embedmd]:# (examples/step1.go /package main/ $) ```go package main @@ -57,6 +58,7 @@ for any other paths in the `"/images/"` subtree. Let's see how to register our `helloHandler` defined above: +[embedmd]:# (examples/step2.go /package main/ $) ```go package main @@ -73,11 +75,14 @@ func main() { http.HandleFunc("/hello", helloHandler) } ``` + Note that we're registering our handler as part of the `main` function. Try to run the code above: - $ go run main.go +```bash +$ go run examples/step2.go +``` What happens? Well, we're missing the last piece of the puzzle: starting the web server! @@ -145,6 +150,7 @@ when no error has occurred the returned value equals to `nil`. So if we want to check that our server started successfully and log an error otherwise we would modify our code to add a call to `ListenAndServe`. +[embedmd]:# (examples/step3.go /package main/ $) ```go package main @@ -167,8 +173,6 @@ func main() { } ``` -_Note_: you can find the complete code on [main.go](main.go). - Running this code should now start a web server listening on `127.0.0.1:8080`. Try it: @@ -200,12 +204,13 @@ These cases can be handled either by hand or using a toolkit that will plug correctly into the existing `net/http` package, such as the [Gorilla toolkit](http://www.gorillatoolkit.org/) and its `mux` package. - +[embedmd]:# (examples/gorilla.go /package main/ $) ```go package main import ( - ... + "log" + "net/http" "github.com/gorilla/mux" ) diff --git a/section02/examples/gorilla.go b/section02/examples/gorilla.go new file mode 100644 index 0000000..708e929 --- /dev/null +++ b/section02/examples/gorilla.go @@ -0,0 +1,52 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to writing, software distributed +// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "log" + "net/http" + + "github.com/gorilla/mux" +) + +func listProducts(w http.ResponseWriter, r *http.Request) { + // list all products +} + +func addProduct(w http.ResponseWriter, r *http.Request) { + // add a product +} + +func getProduct(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["productID"] + // get a specific product +} + +func main() { + r := mux.NewRouter() + // match only GET requests on /product/ + r.HandleFunc("/product/", listProducts).Methods("GET") + + // match only POST requests on /product/ + r.HandleFunc("/product/", addProduct).Methods("POST") + + // match GET regardless of productID + r.HandleFunc("/product/{productID}", getProduct) + + // handle all requests with the Gorilla router. + http.Handle("/", r) + if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil { + log.Fatal(err) + } +} diff --git a/section02/examples/step1.go b/section02/examples/step1.go new file mode 100644 index 0000000..6f03b26 --- /dev/null +++ b/section02/examples/step1.go @@ -0,0 +1,23 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to writing, software distributed +// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, web") +} diff --git a/section02/examples/step2.go b/section02/examples/step2.go new file mode 100644 index 0000000..be20684 --- /dev/null +++ b/section02/examples/step2.go @@ -0,0 +1,27 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to writing, software distributed +// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, web") +} + +func main() { + http.HandleFunc("/hello", helloHandler) +} diff --git a/section02/main.go b/section02/examples/step3.go similarity index 100% rename from section02/main.go rename to section02/examples/step3.go diff --git a/section03/README.md b/section03/README.md index 4895724..69cfb15 100644 --- a/section03/README.md +++ b/section03/README.md @@ -22,9 +22,14 @@ The `http.Request` type has a method `FormValue` with the following docs: That's easy! So if we want to obtain the value of a parameter `q` in the URL `/hello?msg=world` we can write the next program. +[embedmd]:# (examples/hello_parameter.go /func handler/ /^}/) ```go func handler(w http.ResponseWriter, r *http.Request) { - msg := r.FormValue("msg") + name := r.FormValue("name") + if name == "" { + name = "friend" + } + fmt.Fprintf(w, "Hello, %s!", name) } ``` @@ -36,13 +41,15 @@ If the `name` is not present it should print `Hello, friend!`. You can test it with your own browser, but let's try a couple things with `curl` too. Before running these think about what you expect to see and why. - $ curl "localhost:8080/hello?name=world" +```bash +$ curl "localhost:8080/hello?name=world" - $ curl "localhost:8080/hello?name=world&name=francesc" +$ curl "localhost:8080/hello?name=world&name=francesc" - $ curl -X POST -d "name=francesc" "localhost:8080/hello" +$ curl -X POST -d "name=francesc" "localhost:8080/hello" - $ curl -X POST -d "name=francesc" "localhost:8080/hello?name=potato" +$ curl -X POST -d "name=francesc" "localhost:8080/hello?name=potato" +``` Think about how would you make your program print all the values given to `name`. @@ -66,7 +73,9 @@ If the call to `ioutil.ReadAll` returns an error write that error message to the You can test your exercise by using `curl`: - $ curl -X POST -d "francesc" "localhost:8080/hello" +```bash +$ curl -X POST -d "francesc" "localhost:8080/hello" +``` As an extra exercise remove any extra blank spaces surrounding the name. @@ -119,7 +128,7 @@ The answer is that the `net/http` packages guesses that the output is HTML, and therefore your browser concatenates the lines. For short outputs, it's hard to guess. You can see the content type of your response by adding `-v` to your `curl` command. -``` +```bash $ curl -v localhost:8080 < HTTP/1.1 200 OK < Date: Mon, 25 Apr 2016 16:14:46 GMT @@ -134,8 +143,9 @@ You can set headers in the response with the `Header` function in the `ResponseW `Header` returns a [`http.Header`](https://golang.org/pkg/net/http/#Header) which has, among other methods, the method `Set`. We can then set the content type in our `ResponseWriter` named `w` like this. +[embedmd]:# (examples/text_handler.go /w.Header.*/) ```go -r.Header().Set("Content-Type", "text/plain") +w.Header().Set("Content-Type", "text/plain") ``` ### Avoiding repetition @@ -148,29 +158,32 @@ Some people call them decorators, most of them also write Python 😛. To start we're going to define a new type named `textHandler` that contains a `http.HandlerFunc`. +[embedmd]:# (examples/text_handler.go /type textHandler/ /^}/) ```go type textHandler struct { - h http.HandlerFunc + h http.HandlerFunc } ``` Now we're going to define the `ServeHTTP` method on `textHandler` so it satisfies the `http.Handler` interface. +[embedmd]:# (examples/text_handler.go /.*ServeHTTP/ /^}/) ```go func (t textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // Set the content type - w.Header().Set("Content-Type", "text/plain") - // Then call ServeHTTP in the decorated handler. - t.h(w, r) + // Set the content type + w.Header().Set("Content-Type", "text/plain") + // Then call ServeHTTP in the decorated handler. + t.h(w, r) } ``` Finally we replace our `http.HandleFunc` calls with `http.Handle`. +[embedmd]:# (examples/text_handler.go /func main/ /^}/) ```go func main() { - http.Handle("/hello", textHandler{helloHandler}) - http.ListenAndServe(":8080", nil) + http.Handle("/hello", textHandler{helloHandler}) + http.ListenAndServe(":8080", nil) } ``` diff --git a/section03/examples/text_handler.go b/section03/examples/text_handler.go new file mode 100644 index 0000000..3b3e062 --- /dev/null +++ b/section03/examples/text_handler.go @@ -0,0 +1,39 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to writing, software distributed +// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net/http" +) + +type textHandler struct { + h http.HandlerFunc +} + +func (t textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Set the content type + w.Header().Set("Content-Type", "text/plain") + // Then call ServeHTTP in the decorated handler. + t.h(w, r) +} + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "hello") +} + +func main() { + http.Handle("/hello", textHandler{helloHandler}) + http.ListenAndServe(":8080", nil) +} diff --git a/section04/README.md b/section04/README.md index 9b1d154..872b33f 100644 --- a/section04/README.md +++ b/section04/README.md @@ -41,6 +41,7 @@ manage that for you. This simplifies the code we had before: +[embedmd]:# (examples/hello.go /package hello/ $) ```go package hello @@ -82,6 +83,7 @@ $ go get -u google.golang.org/appengine/... To create a new `appengine.Context` we need to call `appengine.NewContext` and pass an HTTP request. +[embedmd]:# (app/app.go /package app/ $) ```go package app @@ -98,13 +100,13 @@ func init() { } func handler(w http.ResponseWriter, r *http.Request) { - // first create a new context + // first create a new context c := appengine.NewContext(r) - // and use that context to create a new http client + // and use that context to create a new http client client := urlfetch.Client(c) - // now we can use that http client as before - res, err := client.Get("https://google.com") + // now we can use that http client as before + res, err := client.Get("http://google.com") if err != nil { http.Error(w, fmt.Sprintf("could not get google: %v", err), http.StatusInternalServerError) return @@ -124,6 +126,7 @@ to what we did with `HandleFunc`. This would be the `app.yaml` file for our Hello, App Engine application. +[embedmd]:# (app/app.yaml) ```yaml runtime: go # the runtime (python, java, go, php) api_version: go1 # the runtime version @@ -139,12 +142,16 @@ Once we have the `main.go` and `app.yaml` files in a directory we can run the application locally by going to the directory and executing the `goapp` tool that comes with the Go SDK for App Engine. - $ goapp serve . +```bash +$ goapp serve . +``` You will see many logs, check for errors, and if everything works fine you will see a message like: - INFO 2016-08-31 15:21:05,793 dispatcher.py:197] Starting module "default" running at: http://localhost:8080 +```bash +INFO 2016-08-31 15:21:05,793 dispatcher.py:197] Starting module "default" running at: http://localhost:8080 +``` Visit http://localhost:8080/hello and you should see your beautiful web app again. @@ -167,14 +174,18 @@ That's it! You can now deploy your code to the Google App Engine servers! Modify the `app.yaml` changing the `application` line to contain the project ID of the project you just created and deploy it running: - $ goapp deploy --version=1 --application=your-project-id . +```bash +$ goapp deploy --version=1 --application=your-project-id . +``` The first time you run `goapp` it will open a browser and go through the authentication and authorization process, this will happen only once. If for some reason you need to do this again you can remove the auth info: - $ rm -f ~/.appcfg_oauth2_tokens +```bash +$ rm -f ~/.appcfg_oauth2_tokens +``` Once this succeeds your app is available on https://your-project-id.appspot.com. diff --git a/section04/app/app.go b/section04/app/app.go index a5cbba6..4cec049 100644 --- a/section04/app/app.go +++ b/section04/app/app.go @@ -32,7 +32,7 @@ func handler(w http.ResponseWriter, r *http.Request) { client := urlfetch.Client(c) // now we can use that http client as before - res, err := client.Get("https://google.com") + res, err := client.Get("http://google.com") if err != nil { http.Error(w, fmt.Sprintf("could not get google: %v", err), http.StatusInternalServerError) return diff --git a/section04/app/app.yaml b/section04/app/app.yaml index 51946a9..4e8789a 100644 --- a/section04/app/app.yaml +++ b/section04/app/app.yaml @@ -1,6 +1,6 @@ -runtime: go -api_version: go1 +runtime: go # the runtime (python, java, go, php) +api_version: go1 # the runtime version handlers: -- url: /.* - script: _go_app +- url: /.* # for all requests + script: _go_app # pass the request to the Go code \ No newline at end of file diff --git a/section04/examples/hello.go b/section04/examples/hello.go new file mode 100644 index 0000000..77a3874 --- /dev/null +++ b/section04/examples/hello.go @@ -0,0 +1,27 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to writing, software distributed +// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. + +package hello + +import ( + "fmt" + "net/http" +) + +func helloHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, "Hello, App Engine") +} + +func init() { + http.HandleFunc("/hello", helloHandler) +} diff --git a/section05/README.md b/section05/README.md index b3e757d..4672719 100644 --- a/section05/README.md +++ b/section05/README.md @@ -7,6 +7,7 @@ make the HTML and JavaScript components communicate with your Go code. For now let's start with a simple HTML page: +[embedmd]:# (all_static/hello.html /.*DOCTYPE/ $) ```html @@ -22,6 +23,7 @@ For now let's start with a simple HTML page: We can create a new `app.yaml` to serve this static page: +[embedmd]:# (all_static/app.yaml) ```yaml runtime: go api_version: go1 @@ -45,18 +47,24 @@ There's two solutions for this: - or add some Go code, something as simple as this `dummy.go` file: +[embedmd]:# (all_static/dummy.go /package dummy/ $) ```go package dummy ``` + We will do the latter as we'll add more Go code later on. Try running your application again: - $ goapp serve . +```bash +$ goapp serve . +``` Or deploying it: - $ goapp deploy --application=your-project-id --version=1 . +```bash +$ goapp deploy --application=your-project-id --version=1 . +``` And verify that the output matches your expectations: @@ -86,6 +94,7 @@ We will need three components: The only part that changes here is the `app.yaml`: +[embedmd]:# (mixed_content/app.yaml) ```yaml runtime: go api_version: go1 @@ -95,7 +104,7 @@ handlers: - url: / static_files: hello.html upload: hello.html -# requests with the hello path are handled by the Go app. +# requests with the /api/ path are handled by the Go app. - url: /api/.* script: _go_app ``` @@ -108,6 +117,8 @@ Why does it fail? Fix it. This can be done in *many* ways, as many as JavaScript frameworks you can think of. For this simple example we will simply use [jQuery](https://jquery.com/). + +[embedmd]:# (mixed_content/hello.html /.*DOCTYPE/ $) ```html @@ -115,20 +126,14 @@ of. For this simple example we will simply use [jQuery](https://jquery.com/). Hello, App Engine -

Hello, App Engine

The backend says:

@@ -150,19 +155,22 @@ there's a much simpler option once you put all of them in a single directory. Your `app.yaml` in this case will look like this: +[embedmd]:# (static_dirs/app.yaml /runtime/ $) ```yaml runtime: go api_version: go1 handlers: -# requests with the prefix /api/ are handled by the Go app. +# requests starting with /api/ are handled by the Go app. - url: /api/.* script: _go_app -# requests on the root path display index.html. + +# if the path is empty show index.html. - url: / static_files: static/index.html upload: static/index.html -# otherwise we try to find the paths in the static directory.. + +# otherwise try to find it in the static directory. - url: / static_dir: static ``` diff --git a/section05/mixed_content/hello.html b/section05/mixed_content/hello.html index 94c677a..5e3e49b 100644 --- a/section05/mixed_content/hello.html +++ b/section05/mixed_content/hello.html @@ -19,7 +19,6 @@ Hello, App Engine -

Hello, App Engine

diff --git a/section06/README.md b/section06/README.md index 7dedacb..04d952e 100644 --- a/section06/README.md +++ b/section06/README.md @@ -26,8 +26,8 @@ We would create a type containing the same fields: ```go type Person struct { - Name string - AgeYears int + Name string + AgeYears int } ``` @@ -41,6 +41,7 @@ for each Go field. For instance we would add the following field tags to the previous example: +[embedmd]:# (examples/app.go /type Person/ /^}/) ```go type Person struct { Name string `json:"name"` @@ -51,28 +52,6 @@ type Person struct { _Note_: the backticks ```(`)``` are just a different way to write strings in Go. They allow you to use double quotes `(")` and to expand across multiple lines. -To declare a new variable of type `Person` we have two options: - -- use the `var` keyword and not provide any initial value for its fields, - -```go - var p Person - fmt.Println(p) - // output: Person{ 0} - - // a better way to print structs - fmt.Printf("%#v\n", p) - // output: main.Person{Name:"", AgeYears:0} -``` - -- or use the `:=` operator and initialize the fields. - -```go - p := Person{Name: "gopher", AgeYears: 5} - fmt.Printf("%#v\n", p) - // output: main.Person{Name:"gopher", AgeYears:5} -``` - For more info on structs read [this section](https://tour.golang.org/moretypes/5) of the Go tour. @@ -82,16 +61,9 @@ To encode a Go struct we use a [`json.Encoder`](https://golang.org/pkg/encoding/json#Encoder), which provides a handy `Encode` method. +[embedmd]:# (examples/app.go /func encode/ /^}/) ```go -package main - -import ( - "encoding/json" - "log" - "os" -) - -func main() { +func encode() { p := Person{"gopher", 5} // create an encoder that will write on the standard output. @@ -117,18 +89,9 @@ You can try the code with the `go run` tool, or using the Go playground The same way we have a `json.Encoder` we have a `json.Decoder` and its usage is very similar. +[embedmd]:# (examples/app.go /func decode/ /^}/) ```go - -package main - -import ( - "encoding/json" - "fmt" - "log" - "os" -) - -func main() { +func decode() { // create an empty Person value. var p Person @@ -167,8 +130,9 @@ therefore satisfies the `io.Writer` interface required by `json.NewEncoder`. So we can easily JSON encode a `Person` on an HTTP response: +[embedmd]:# (examples/app.go /func encodeHandler/ /^}/) ```go -func handler(w http.ResponseWriter, r *http.Request) { +func encodeHandler(w http.ResponseWriter, r *http.Request) { p := Person{"gopher", 5} // set the Content-Type header. @@ -177,7 +141,7 @@ func handler(w http.ResponseWriter, r *http.Request) { // encode p to the output. enc := json.NewEncoder(w) err := enc.Encode(p) - if err != nil { + if err != nil { // if encoding fails, create an error page with code 500. http.Error(w, err.Error(), http.StatusInternalServerError) } @@ -193,8 +157,9 @@ Since the signature of the method `Read` matches the one in `io.Reader` we can say that `io.ReadCloser` is an `io.Reader` and therefore we can use the `Body` of a `http.Request` as the input of a `json.Decoder`. +[embedmd]:# (examples/app.go /func decodeHandler/ /^}/) ```go -func handler(w http.ResponseWriter, r *http.Request) { +func decodeHandler(w http.ResponseWriter, r *http.Request) { var p Person dec := json.NewDecoder(r.Body) @@ -209,8 +174,10 @@ func handler(w http.ResponseWriter, r *http.Request) { If you want to test this handler you can use curl: - $ curl -d '{"name": "gopher", "age_years": 5}' http://localhost:8080/ - Name is gopher and age is 5 +```bash +$ curl -d '{"name": "gopher", "age_years": 5}' http://localhost:8080/ +Name is gopher and age is 5 +``` ## Exercise diff --git a/section06/examples/app.go b/section06/examples/app.go index edff3f5..9846558 100644 --- a/section06/examples/app.go +++ b/section06/examples/app.go @@ -16,15 +16,54 @@ package app import ( "encoding/json" "fmt" + "log" "net/http" + "os" ) +// A sample JSON equivalent +/* +{ + "name": "gopher", + "age_years": 5 +} +*/ + // Person contains all the information I can imagine about a person right now. type Person struct { Name string `json:"name"` AgeYears int `json:"age_years"` } +func encode() { + p := Person{"gopher", 5} + + // create an encoder that will write on the standard output. + enc := json.NewEncoder(os.Stdout) + // use the encoder to encode p, which could fail. + err := enc.Encode(p) + // if it failed, log the error and stop execution. + if err != nil { + log.Fatal(err) + } +} + +func decode() { + // create an empty Person value. + var p Person + + // create a decoder reading from the standard input. + dec := json.NewDecoder(os.Stdin) + // use the decoder to decode a value into p. + err := dec.Decode(&p) + // if it failed, log the error and stop execution. + if err != nil { + log.Fatal(err) + } + // otherwise log what we decoded. + fmt.Printf("decoded: %#v\n", p) +} + func encodeHandler(w http.ResponseWriter, r *http.Request) { p := Person{"gopher", 5} @@ -55,4 +94,5 @@ func decodeHandler(w http.ResponseWriter, r *http.Request) { func init() { http.HandleFunc("/encode", encodeHandler) http.HandleFunc("/decode", decodeHandler) + log.Fatal(http.ListenAndServe("localhost:8080", nil)) } diff --git a/section07/README.md b/section07/README.md index 5849825..c6eb577 100644 --- a/section07/README.md +++ b/section07/README.md @@ -61,40 +61,27 @@ func Put(c appengine.Context, key *Key, src interface{}) (*Key, error) - The first parameter is an `appengine.Context` which is the way we link all the operations that are related to a given request together. -- The second parameter is a ```*datastore.Key``` and you can see how to create +- The second parameter is a `*datastore.Key` and you can see how to create one in the code snippet below. - Finally, the last parameter is the value to be stored. -- The function returns another ```*datastore.Key``` and an error which will be +- The function returns another `*datastore.Key` and an error which will be non nil if some error occurs while storing the data. Let's see an example of how to use the `datastore.Put` function: -```go -package app - -import ( - "fmt" - "net/http" - "google.golang.org/appengine" - "google.golang.org/appengine/datastore" -) - -// Person contains the name and age of a person. -type Person struct { - Name string - AgeYears int -} +[embedmd]:# (examples/app.go /func incomplete/ /^}/) +```go +func incompleteHandler(w http.ResponseWriter, r *http.Request) { + // create a new App Engine context from the HTTP request. + ctx := appengine.NewContext(r) -func handler(w http.ResponseWriter, r *http.Request) { p := &Person{Name: "gopher", AgeYears: 5} - // create a new App Engine context from the HTTP request. - ctx := appengine.NewContext(r) - // create a new complete key of kind Person and value gopher. - key := datastore.NewKey(ctx, "Person", "gopher", 0, nil) + // create a new complete key of kind Person. + key := datastore.NewIncompleteKey(ctx, "Person", nil) // put p in the datastore. key, err := datastore.Put(ctx, key, p) if err != nil { @@ -138,12 +125,14 @@ Note that there's no `stringID` or `intID` in this function, as the final value of the key will be decided once we put the value in the datastore. The final value can be obtained by using the returned key by `datastore.Put`. +[embedmd]:# (examples/app.go /func incompleteHandler/ /^}/) ```go -func handler(w http.ResponseWriter, r *http.Request) { - p := &Person{"gopher", 5} - +func incompleteHandler(w http.ResponseWriter, r *http.Request) { // create a new App Engine context from the HTTP request. ctx := appengine.NewContext(r) + + p := &Person{Name: "gopher", AgeYears: 5} + // create a new complete key of kind Person. key := datastore.NewIncompleteKey(ctx, "Person", nil) // put p in the datastore. @@ -190,15 +179,20 @@ func Get(c appengine.Context, key *Key, dst interface{}) error The last parameter should be a pointer to a struct containing the fields that we want to retrieve, for instance: +[embedmd]:# (examples/app.go /func getHandler/ /^}/) ```go -ctx := appengine.NewContext(r) +func getHandler(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) -key := datastore.NewKey(ctx, "Person", "gopher", 0, nil) + key := datastore.NewKey(ctx, "Person", "gopher", 0, nil) -var p Person -err := datastore.Get(ctx, key, &p) -if err != nil { - // handle the error + var p Person + err := datastore.Get(ctx, key, &p) + if err != nil { + http.Error(w, "Person not found", http.StatusNotFound) + return + } + fmt.Fprintln(w, p) } ``` @@ -263,27 +257,43 @@ all the results matching the query. Let's see an example where we retrieve all the values of _kind_ `Person` that are 10 years old or younger ordered by their name. +[embedmd]:# (examples/app.go /queryHandler/ /^}/) ```go -ctx := appengine.NewContext(r) +queryHandler(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) -var p []Person + var p []Person -// create a new query on the kind Person -q := datastore.NewQuery("Person") + // create a new query on the kind Person + q := datastore.NewQuery("Person") -// select only values where field Age is 10 or lower -q = q.Filter("Age <=", 10) + // select only values where field Age is 10 or lower + q = q.Filter("Age <=", 10) -// order all the values by the Name field -q = q.Order("Name") + // order all the values by the Name field + q = q.Order("Name") -// and finally execute the query retrieving all values into p. -_, err := q.GetAll(ctx, &p) -if err != nil { - // handle the error + // and finally execute the query retrieving all values into p. + _, err := q.GetAll(ctx, &p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprintln(w, p) } ``` +Note that `Filter` and `Order` return a `*datastore.Query`. This means you can +chain those operations into a single expression. + +[embedmd]:# (examples/app.go /q :=.*\.$/ /^$/) +```go +q := datastore.NewQuery("Person"). + Filter("Age <=", 10). + Order("Name") +``` + + You can find more information about Datastore Query [here](https://cloud.google.com/appengine/docs/go/datastore/queries). diff --git a/section07/examples/app.go b/section07/examples/app.go index 3c9f34a..ef1c949 100644 --- a/section07/examples/app.go +++ b/section07/examples/app.go @@ -61,7 +61,63 @@ func incompleteHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "gopher stored with key %v", key) } +func getHandler(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + key := datastore.NewKey(ctx, "Person", "gopher", 0, nil) + + var p Person + err := datastore.Get(ctx, key, &p) + if err != nil { + http.Error(w, "Person not found", http.StatusNotFound) + return + } + fmt.Fprintln(w, p) +} + +func queryHandler(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + var p []Person + + // create a new query on the kind Person + q := datastore.NewQuery("Person") + + // select only values where field Age is 10 or lower + q = q.Filter("Age <=", 10) + + // order all the values by the Name field + q = q.Order("Name") + + // and finally execute the query retrieving all values into p. + _, err := q.GetAll(ctx, &p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + fmt.Fprintln(w, p) +} + +func chainedQueryHandler(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + var p []Person + + // create a new query on the kind Person + q := datastore.NewQuery("Person"). + Filter("Age <=", 10). + Order("Name") + + // and finally execute the query retrieving all values into p. + _, err := q.GetAll(ctx, &p) + if err != nil { + // handle the error + } +} + func init() { http.HandleFunc("/complete", completeHandler) http.HandleFunc("/incomplete", incompleteHandler) + http.HandleFunc("/query", queryHandler) + http.HandleFunc("/chainedQuery", chainedQueryHandler) } diff --git a/section08/README.md b/section08/README.md index d4ec7e9..53af381 100644 --- a/section08/README.md +++ b/section08/README.md @@ -18,7 +18,19 @@ func Client(context appengine.Context) *http.Client So given an `appengine.Context` you get an HTTP client, and then you can start from there. So if you wanted to fetch Google's home page you would do: +[embedmd]:# (fetch/fetch.go /package fetch/ /^}/) ```go +package fetch + +import ( + "io" + "net/http" + + "google.golang.org/appengine" + "google.golang.org/appengine/log" + "google.golang.org/appengine/urlfetch" +) + func handler(w http.ResponseWriter, r *http.Request) { ctx := appengine.NewContext(r) @@ -29,7 +41,7 @@ func handler(w http.ResponseWriter, r *http.Request) { res, err := c.Get("https://google.com") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - return + return } // we need to close the body at the end of this function diff --git a/section09/README.md b/section09/README.md index e5a9c98..d0f2219 100644 --- a/section09/README.md +++ b/section09/README.md @@ -53,18 +53,36 @@ type Item struct { So if we want to store an item with key "name" and value "gopher" that will be valid for one hour we could write: +[embedmd]:# (getset/app.go /package app/ /^}/) ```go -ctx := appengine.NewContext(r) - -item := &memcache.Item{ - Key: "name", - Value: []byte("gopher"), - Expiration: 1 * time.Hour, -} - -err := memcache.Set(ctx, item) -if err != nil { - // handle the error +package app + +import ( + "fmt" + "net/http" + "time" + + "google.golang.org/appengine" + "google.golang.org/appengine/memcache" +) + +func set(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + // get the parameters k and v from the request + key := r.FormValue("k") + value := r.FormValue("v") + + item := &memcache.Item{ + Key: key, + Value: []byte(value), + Expiration: 1 * time.Hour, + } + + err := memcache.Set(ctx, item) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } } ``` @@ -82,18 +100,22 @@ returned error is `memcache.ErrCacheMiss`. Let's retrieve the value we cached on the previous code: +[embedmd]:# (getset/app.go /func get/ /^}/) ```go -ctx := appengine.NewContext(r) - -item, err := memcache.Get(ctx, "name") -switch err { -case nil: - fmt.Fprintln(w, "name is %s", item.Value) -case memcache.ErrCacheMiss: - // the key was not found, this could be fine - return -default: - // there was an actual error, is Memcache down? +func get(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + key := r.FormValue("k") + + item, err := memcache.Get(ctx, key) + switch err { + case nil: + fmt.Fprintf(w, "%s", item.Value) + case memcache.ErrCacheMiss: + fmt.Fprint(w, "key not found") + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + } } ``` @@ -111,29 +133,50 @@ When using codecs rather than setting the `Value` field of the `memcache.Item` you should use the `Object` field instead. For instance you can cache and retrieve a `Person` using the JSON codec like this: +[embedmd]:# (codec/app.go /func set/ /^}/) ```go -p := Person{ - Name: "gopher", - AgeYears: 5, +func set(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + var p Person + if err := json.NewDecoder(r.Body).Decode(&p); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + item := &memcache.Item{ + Key: "last_person", + Object: p, // we set the Object field instead of Value + Expiration: 1 * time.Hour, + } + + // we use the JSON codec + err := memcache.JSON.Set(ctx, item) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } } - -item := &memcache.Item{ - Key: "last_person", - Value: p, - Expiration: 1 * time.Hour, -} - -// we use the JSON codec -err := memcache.JSON.Set(ctx, item) -// handle the error ``` And to retrieve it it's even simpler: +[embedmd]:# (codec/app.go /func get/ /^}/) ```go -var p Person -_, err := memcache.JSON.Get(ctx, "last_person", &p) -// handle the error +func get(w http.ResponseWriter, r *http.Request) { + ctx := appengine.NewContext(r) + + var p Person + _, err := memcache.JSON.Get(ctx, "last_person", &p) + if err == nil { + json.NewEncoder(w).Encode(p) + return + } + if err == memcache.ErrCacheMiss { + fmt.Fprint(w, "key not found") + return + } + http.Error(w, err.Error(), http.StatusInternalServerError) +} ``` You can see a complete example of an application using the JSON code diff --git a/section09/codec/app.go b/section09/codec/app.go index 767b672..b4b00f2 100644 --- a/section09/codec/app.go +++ b/section09/codec/app.go @@ -55,14 +55,15 @@ func get(w http.ResponseWriter, r *http.Request) { var p Person _, err := memcache.JSON.Get(ctx, "last_person", &p) - switch err { - case nil: + if err == nil { json.NewEncoder(w).Encode(p) - case memcache.ErrCacheMiss: + return + } + if err == memcache.ErrCacheMiss { fmt.Fprint(w, "key not found") - default: - http.Error(w, err.Error(), http.StatusInternalServerError) + return } + http.Error(w, err.Error(), http.StatusInternalServerError) } func init() { From 7234d77a2303e53a2dd248e6f0178dff3c889e5f Mon Sep 17 00:00:00 2001 From: Francesc Campoy Date: Sat, 25 Feb 2017 10:46:56 +0530 Subject: [PATCH 3/5] update copyright to 2017 Change-Id: I11a24cf689f32c030cc36749e9645dcaa2e9641b --- events/step0/events.go | 2 +- events/step0/static/index.html | 2 +- events/step0/static/script.js | 2 +- events/step0/static/style.css | 2 +- events/step1/events.go | 2 +- events/step1/static/index.html | 2 +- events/step1/static/script.js | 2 +- events/step1/static/style.css | 2 +- events/step2/events.go | 2 +- events/step2/static/index.html | 2 +- events/step2/static/script.js | 2 +- events/step2/static/style.css | 2 +- events/step3/events.go | 2 +- events/step3/static/index.html | 2 +- events/step3/static/script.js | 2 +- events/step3/static/style.css | 2 +- events/step3/weather.go | 2 +- events/step4/events.go | 2 +- events/step4/static/index.html | 2 +- events/step4/static/script.js | 2 +- events/step4/static/style.css | 2 +- events/step4/weather.go | 2 +- events/step5/events.go | 2 +- events/step5/static/index.html | 2 +- events/step5/static/script.js | 2 +- events/step5/static/style.css | 2 +- events/step5/weather.go | 2 +- section00/README.md | 7 +++---- section00/hello/main.go | 9 ++++----- section01/examples/do-get.go | 2 +- section01/examples/get.go | 2 +- section02/examples/gorilla.go | 2 +- section02/examples/step1.go | 2 +- section02/examples/step2.go | 2 +- section02/examples/step3.go | 2 +- section03/examples/hello_body.go | 2 +- section03/examples/hello_parameter.go | 2 +- section03/examples/text_handler.go | 2 +- section04/app/app.go | 2 +- section04/examples/hello.go | 2 +- section05/all_static/dummy.go | 2 +- section05/all_static/hello.html | 2 +- section05/mixed_content/hello.go | 2 +- section05/mixed_content/hello.html | 2 +- section05/static_dirs/hello.go | 2 +- section05/static_dirs/static/index.html | 2 +- section05/static_dirs/static/script.js | 2 +- section05/static_dirs/static/style.css | 2 +- section06/examples/app.go | 2 +- section07/examples/app.go | 2 +- section08/fetch/fetch.go | 2 +- section09/codec/app.go | 2 +- section09/getset/app.go | 2 +- utils/http-methods/app.go | 2 +- 54 files changed, 59 insertions(+), 61 deletions(-) diff --git a/events/step0/events.go b/events/step0/events.go index 360d82d..60c6eb8 100644 --- a/events/step0/events.go +++ b/events/step0/events.go @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. All rights reserved. +// Copyright 2017 Google Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/events/step0/static/index.html b/events/step0/static/index.html index 1e08fd0..056d02c 100644 --- a/events/step0/static/index.html +++ b/events/step0/static/index.html @@ -1,5 +1,5 @@