{{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |├── .gitignore ├── section06 ├── .DS_Store ├── examples │ ├── app.yaml │ └── app.go └── README.md ├── section07 ├── local.png ├── examples │ └── app.go └── README.md ├── section09 ├── local.png ├── console.png ├── codec │ ├── app.yaml │ └── app.go ├── getset │ ├── app.yaml │ └── app.go └── README.md ├── events ├── screenshot.png ├── step3 │ ├── bad_api_output.json │ ├── app.yaml │ ├── good_api_output.json │ ├── README.md │ ├── static │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ ├── weather.go │ └── events.go ├── step2 │ ├── datastore.png │ ├── app.yaml │ ├── README.md │ ├── static │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ └── events.go ├── step0 │ ├── app.yaml │ ├── static │ │ ├── output.json │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ ├── README.md │ └── events.go ├── step1 │ ├── app.yaml │ ├── README.md │ ├── static │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ └── events.go ├── step5 │ ├── README.md │ ├── app.yaml │ ├── static │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ ├── weather.go │ └── events.go ├── step4 │ ├── app.yaml │ ├── README.md │ ├── static │ │ ├── script.js │ │ ├── style.css │ │ └── index.html │ ├── weather.go │ └── events.go └── README.md ├── section04 ├── img │ ├── plus.png │ ├── equals.png │ ├── gopher.png │ ├── gaegopher.jpg │ └── app-engine-logo.png ├── app │ ├── app.yaml │ └── app.go ├── examples │ └── hello.go └── README.md ├── section05 ├── screenshot.png ├── all_static │ ├── app.yaml │ ├── dummy.go │ └── hello.html ├── mixed_content │ ├── app.yaml │ ├── hello.go │ └── hello.html ├── static_dirs │ ├── app.yaml │ ├── static │ │ ├── style.css │ │ ├── script.js │ │ └── index.html │ └── hello.go └── README.md ├── utils └── http-methods │ ├── app.yaml │ └── app.go ├── .travis.yml ├── section08 ├── fetch │ ├── app.yaml │ └── fetch.go └── README.md ├── section00 ├── hello │ └── main.go └── README.md ├── section02 ├── examples │ ├── step1 │ │ └── main.go │ ├── step2 │ │ └── main.go │ ├── step3 │ │ └── main.go │ └── gorilla.go └── README.md ├── section01 ├── examples │ ├── get.go │ └── do-get.go └── README.md ├── section03 ├── examples │ ├── texthandler │ │ └── main.go │ └── handlers │ │ └── main.go └── README.md ├── CONTRIBUTING.md ├── section10 └── README.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | */.DS_Store -------------------------------------------------------------------------------- /section06/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section06/.DS_Store -------------------------------------------------------------------------------- /section07/local.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section07/local.png -------------------------------------------------------------------------------- /section09/local.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section09/local.png -------------------------------------------------------------------------------- /events/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/events/screenshot.png -------------------------------------------------------------------------------- /section04/img/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section04/img/plus.png -------------------------------------------------------------------------------- /section09/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section09/console.png -------------------------------------------------------------------------------- /events/step3/bad_api_output.json: -------------------------------------------------------------------------------- 1 | { 2 | "cod": "404", 3 | "message": "Error: Not found city" 4 | } -------------------------------------------------------------------------------- /section04/img/equals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section04/img/equals.png -------------------------------------------------------------------------------- /section04/img/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section04/img/gopher.png -------------------------------------------------------------------------------- /section05/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section05/screenshot.png -------------------------------------------------------------------------------- /events/step2/datastore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/events/step2/datastore.png -------------------------------------------------------------------------------- /section04/img/gaegopher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section04/img/gaegopher.jpg -------------------------------------------------------------------------------- /section06/examples/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | -------------------------------------------------------------------------------- /utils/http-methods/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /.* 6 | script: _go_app 7 | -------------------------------------------------------------------------------- /section04/img/app-engine-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/campoy/go-web-workshop/HEAD/section04/img/app-engine-logo.png -------------------------------------------------------------------------------- /section05/all_static/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /hello 6 | static_files: hello.html 7 | upload: hello.html 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | install: 7 | - go get github.com/campoy/embedmd 8 | script: 9 | - embedmd -d **/*.md 10 | - go get ./...&& go test ./... 11 | -------------------------------------------------------------------------------- /events/step0/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /api/.* 6 | script: _go_app 7 | - url: / 8 | static_files: static/index.html 9 | upload: static/index.html 10 | - url: / 11 | static_dir: static 12 | -------------------------------------------------------------------------------- /events/step1/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /api/.* 6 | script: _go_app 7 | - url: / 8 | static_files: static/index.html 9 | upload: static/index.html 10 | - url: / 11 | static_dir: static 12 | -------------------------------------------------------------------------------- /events/step2/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /api/.* 6 | script: _go_app 7 | - url: / 8 | static_files: static/index.html 9 | upload: static/index.html 10 | - url: / 11 | static_dir: static 12 | -------------------------------------------------------------------------------- /events/step5/README.md: -------------------------------------------------------------------------------- 1 | # Step 5: final version of the application 2 | 3 | Congratulations, your application is now complete!!!! 🎉 4 | 5 | You might continue improving it; one idea is to perform all 6 | the calls to `weather` concurrently. How would you do that? 7 | -------------------------------------------------------------------------------- /section04/app/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go # the runtime (python, java, go, php) 2 | api_version: go1 # the runtime version 3 | 4 | handlers: 5 | - url: /.* # for all requests 6 | script: _go_app # pass the request to the Go code -------------------------------------------------------------------------------- /section08/fetch/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go # the runtime (python, java, go, php) 2 | api_version: go1 # the runtime version 3 | 4 | handlers: 5 | - url: /.* # for all requests 6 | script: _go_app # pass the request to the Go code 7 | 8 | -------------------------------------------------------------------------------- /section09/codec/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go # the runtime (python, java, go, php) 2 | api_version: go1 # the runtime version 3 | 4 | handlers: 5 | - url: /.* # for all requests 6 | script: _go_app # pass the request to the Go code 7 | 8 | -------------------------------------------------------------------------------- /section09/getset/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go # the runtime (python, java, go, php) 2 | api_version: go1 # the runtime version 3 | 4 | handlers: 5 | - url: /.* # for all requests 6 | script: _go_app # pass the request to the Go code 7 | 8 | -------------------------------------------------------------------------------- /section05/mixed_content/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | # requests with empty paths are shown the html page. 6 | - url: / 7 | static_files: hello.html 8 | upload: hello.html 9 | # requests with the /api/ path are handled by the Go app. 10 | - url: /api/.* 11 | script: _go_app 12 | -------------------------------------------------------------------------------- /events/step4/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /api/.* 6 | script: _go_app 7 | - url: / 8 | static_files: static/index.html 9 | upload: static/index.html 10 | - url: / 11 | static_dir: static 12 | 13 | # Sign up to openweathermap.org and obtain a new API key, then replace the value of WEATHER_API_KEY. 14 | env_variables: 15 | WEATHER_API_KEY: 'get your own!' -------------------------------------------------------------------------------- /events/step3/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /api/.* 6 | script: _go_app 7 | - url: / 8 | static_files: static/index.html 9 | upload: static/index.html 10 | - url: / 11 | static_dir: static 12 | 13 | # Sign up to openweathermap.org and obtain a new API key, then replace the value of WEATHER_API_KEY. 14 | env_variables: 15 | WEATHER_API_KEY: 'get your own!' 16 | -------------------------------------------------------------------------------- /events/step5/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /api/.* 6 | script: _go_app 7 | - url: / 8 | static_files: static/index.html 9 | upload: static/index.html 10 | - url: / 11 | static_dir: static 12 | 13 | # Sign up to openweathermap.org and obtain a new API key, then replace the value of WEATHER_API_KEY. 14 | env_variables: 15 | WEATHER_API_KEY: 'get your own!' 16 | -------------------------------------------------------------------------------- /section05/static_dirs/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | # requests starting with /api/ are handled by the Go app. 6 | - url: /api/.* 7 | script: _go_app 8 | 9 | # if the path is empty show index.html. 10 | - url: / 11 | static_files: static/index.html 12 | upload: static/index.html 13 | 14 | # otherwise try to find it in the static directory. 15 | - url: / 16 | static_dir: static 17 | -------------------------------------------------------------------------------- /events/step1/README.md: -------------------------------------------------------------------------------- 1 | # Step 1: JSON and local storage 2 | 3 | The goal of this step is to start encoding and encoding events. 4 | For now they will be stored in a global variable protected by a mutex (to avoid data races). 5 | This is not optimal because at any point the instance could disappear and we would lose our data. 6 | 7 | Don't worry too much about that, the next step takes care of storage with datastore. 8 | 9 | Once done, go back to the [JSON section](../../section06/README.md#congratulations). 10 | -------------------------------------------------------------------------------- /section05/all_static/dummy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package dummy 15 | -------------------------------------------------------------------------------- /section05/static_dirs/static/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to writing, software distributed 9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | * CONDITIONS OF ANY KIND, either express or implied. 11 | * 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | h1 { 17 | text-align: center; 18 | color: #a74; 19 | } 20 | -------------------------------------------------------------------------------- /section05/static_dirs/static/script.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Google Inc. All rights reserved. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to writing, software distributed 9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | * CONDITIONS OF ANY KIND, either express or implied. 11 | * 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | $(function() { 17 | $('#message').load('/api/hello'); 18 | }); 19 | -------------------------------------------------------------------------------- /section00/hello/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "fmt" 17 | 18 | func main() { 19 | fmt.Println("hello, world!") } 20 | -------------------------------------------------------------------------------- /events/step4/README.md: -------------------------------------------------------------------------------- 1 | # Step 4: storing temporary results in Memcache 2 | 3 | In the previous step we ended up with an application that was wasting time 4 | and network resources by sending a new request to the weather API for each event, 5 | every time someone listed the events. 6 | 7 | Now we will fix that by using App Engine Memcache. This is simply a fully 8 | managed Memcache instance, you don't need to do anything to start using it! 9 | Isn't that cool? 10 | 11 | Once you're done with this exercise you will see that your application is much faster, 12 | and it consumes less resources. 13 | 14 | Congratulations, you're done! You can check your code comparing it to the one in 15 | [step 5](../step5), or go back to the [instructions](../../section09/README.md#congratulations) 16 | for the last instructions. 17 | -------------------------------------------------------------------------------- /section05/all_static/hello.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 |
20 |
11 |
12 | Once you're done, go back to the instructions on [datastore](../../section07/README.md#congratulations).
13 |
14 | [1]: https://cloud.google.com/appengine/docs/go/datastore/
15 | [2]: https://cloud.google.com/appengine/docs/go/datastore/reference#Put
16 | [3]: https://cloud.google.com/appengine/docs/go/datastore/queries
17 | [4]: http://localhost:8000/datastore?kind=Event
18 | [5]: https://console.cloud.google.com/datastore
19 |
--------------------------------------------------------------------------------
/events/README.md:
--------------------------------------------------------------------------------
1 | # Events
2 |
3 | The goal of this workshop is to create and deploy a web application from scratch,
4 | using Go and Google App Engine.
5 |
6 | The application is an event manager with weather information for each event. Cool, right?
7 | It doesn't look amazing, but hey ... I'm *really* not a frontend engineer. 😅
8 |
9 |
10 |
11 | The implementation will be done in six steps starting, obviously, with step 0.
12 |
13 | - [Step 0: basic architecture](step0/README.md)
14 | - [Step 1: JSON and local storage](step1/README.md)
15 | - [Step 2: durable storage](step2/README.md)
16 | - [Step 3: adding weather with openweathermap.org](step3/README.md)
17 | - [Step 4: storing temporary results in Memcache](step4/README.md)
18 | - [Step 5: final version of the application](step5/README.md)
19 |
20 | Everything you need to do corresponds to a comment in the code, so don't miss any!
21 |
--------------------------------------------------------------------------------
/section02/examples/step1/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package main
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | )
20 |
21 | func helloHandler(w http.ResponseWriter, r *http.Request) {
22 | fmt.Fprintln(w, "Hello, web")
23 | }
24 |
25 | // This doesn't appear on the markdown docs.
26 | func main() {}
27 |
--------------------------------------------------------------------------------
/section02/examples/step2/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package main
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | )
20 |
21 | func helloHandler(w http.ResponseWriter, r *http.Request) {
22 | fmt.Fprintln(w, "Hello, web")
23 | }
24 |
25 | func main() {
26 | http.HandleFunc("/hello", helloHandler)
27 | }
28 |
--------------------------------------------------------------------------------
/events/step3/good_api_output.json:
--------------------------------------------------------------------------------
1 | {
2 | "coord": {
3 | "lon": 19.04,
4 | "lat": 47.5
5 | },
6 | "weather": [
7 | {
8 | "id": 800,
9 | "main": "Clear",
10 | "description": "clear sky",
11 | "icon": "01n"
12 | }
13 | ],
14 | "base": "cmc stations",
15 | "main": {
16 | "temp": 277,
17 | "pressure": 1007,
18 | "humidity": 93,
19 | "temp_min": 275.37,
20 | "temp_max": 278.15
21 | },
22 | "wind": {
23 | "speed": 1.5
24 | },
25 | "clouds": {
26 | "all": 0
27 | },
28 | "dt": 1461611729,
29 | "sys": {
30 | "type": 1,
31 | "id": 5724,
32 | "message": 0.0103,
33 | "country": "HU",
34 | "sunrise": 1461555317,
35 | "sunset": 1461606530
36 | },
37 | "id": 3054643,
38 | "name": "Budapest",
39 | "cod": 200
40 | }
--------------------------------------------------------------------------------
/section04/examples/hello.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package hello
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | )
20 |
21 | func helloHandler(w http.ResponseWriter, r *http.Request) {
22 | fmt.Fprintln(w, "Hello, App Engine")
23 | }
24 |
25 | func init() {
26 | http.HandleFunc("/hello", helloHandler)
27 | }
28 |
--------------------------------------------------------------------------------
/section05/mixed_content/hello.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package hello
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | )
20 |
21 | func helloHandler(w http.ResponseWriter, r *http.Request) {
22 | fmt.Fprintln(w, "Hello, Backend")
23 | }
24 |
25 | func init() {
26 | http.HandleFunc("/api/hello", helloHandler)
27 | }
28 |
--------------------------------------------------------------------------------
/section05/static_dirs/hello.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package hello
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | )
20 |
21 | func helloHandler(w http.ResponseWriter, r *http.Request) {
22 | fmt.Fprintln(w, "Hello, Backend")
23 | }
24 |
25 | func init() {
26 | http.HandleFunc("/api/hello", helloHandler)
27 | }
28 |
--------------------------------------------------------------------------------
/section01/examples/get.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package main
15 |
16 | import (
17 | "fmt"
18 | "log"
19 | "net/http"
20 | )
21 |
22 | func main() {
23 | // try changing the value of this url
24 | res, err := http.Get("https://golang.org")
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 | fmt.Println(res.Status)
29 | }
30 |
--------------------------------------------------------------------------------
/events/step3/README.md:
--------------------------------------------------------------------------------
1 | # Step 3: adding weather with openweathermap.org
2 |
3 | So far, we have an application that is capable of storing events submitted
4 | through a web form, and display them back into the web page.
5 |
6 | Now we're going to add some cool functionality by retrieving the weather for
7 | the location where the event is taking place. To do so we need to fetch
8 | information from an external API, and we will use open openweathermap.org.
9 |
10 | You need to sign up to https://openweathermap.org to obtain an API key and replace the
11 | value of WEATHER_API_KEY in the app.yaml file.
12 |
13 | Then you'll implement the call to the API, preparing the request, decoding the
14 | response, and adding the resulting weather to the events.
15 |
16 | This will cause one API call per event per request, which is incredibly wasteful.
17 | But don't worry we'll fix that later, for now come back to the
18 | [instructions](../../section08/README.md#congratulations).
19 |
--------------------------------------------------------------------------------
/section02/examples/step3/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package main
15 |
16 | import (
17 | "fmt"
18 | "log"
19 | "net/http"
20 | )
21 |
22 | func helloHandler(w http.ResponseWriter, r *http.Request) {
23 | fmt.Fprintln(w, "Hello, web")
24 | }
25 |
26 | func main() {
27 | http.HandleFunc("/hello", helloHandler)
28 | err := http.ListenAndServe("127.0.0.1:8080", nil)
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/events/step0/README.md:
--------------------------------------------------------------------------------
1 | # Step 0: basic architecture
2 |
3 | The goal for this step is to define the two entry points to your web application.
4 |
5 | You can try running before you start coding to see the current behavior.
6 |
7 | $ dev_appserver.py .
8 |
9 | Once you've implemented this, visiting `localhost:8080` should display a list of conferences,
10 | and clicking "New Event" should not do anything.
11 |
12 | In order to deploy you'll need to first install and configure [gcloud](https://cloud.google.com/sdk/downloads):
13 |
14 | $ gcloud init
15 |
16 | _Note_: You do not need to set up any Google Compute Engine zone.
17 |
18 | Then simply run
19 |
20 | $ gcloud app deploy --version=step0 app.yaml
21 |
22 | And then visit https://step0.your-project-id.appspot.com or running
23 |
24 | $ gcloud app browse --version=step0
25 |
26 | This will display a basic Events page but also an alert will be alerted. That's fine, for now.
27 |
28 | Once you're done, let's go back to the [instructions](../../section05/README.md#congratulations).
29 |
--------------------------------------------------------------------------------
/section05/static_dirs/static/index.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 | The backend says:
28 | 29 | 30 | -------------------------------------------------------------------------------- /section05/mixed_content/hello.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 |The backend says:
26 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /section01/examples/do-get.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "log" 19 | "net/http" 20 | ) 21 | 22 | func doGet() { 23 | req, err := http.NewRequest("GET", "https://golang.org", nil) 24 | if err != nil { 25 | log.Fatalf("could not create request: %v", err) 26 | } 27 | client := http.DefaultClient 28 | res, err := client.Do(req) 29 | if err != nil { 30 | log.Fatalf("http request failed: %v", err) 31 | } 32 | fmt.Println(res.Status) 33 | } 34 | -------------------------------------------------------------------------------- /section03/examples/texthandler/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "net/http" 19 | ) 20 | 21 | type textHandler struct { 22 | h http.HandlerFunc 23 | } 24 | 25 | func (t textHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 26 | // Set the content type 27 | w.Header().Set("Content-Type", "text/plain") 28 | // Then call ServeHTTP in the decorated handler. 29 | t.h(w, r) 30 | } 31 | 32 | func helloHandler(w http.ResponseWriter, r *http.Request) { 33 | fmt.Fprintln(w, "hello") 34 | } 35 | 36 | func main() { 37 | http.Handle("/hello", textHandler{helloHandler}) 38 | http.ListenAndServe(":8080", nil) 39 | } 40 | -------------------------------------------------------------------------------- /section04/app/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package app 15 | 16 | import ( 17 | "fmt" 18 | "net/http" 19 | 20 | "google.golang.org/appengine" 21 | "google.golang.org/appengine/urlfetch" 22 | ) 23 | 24 | func init() { 25 | http.HandleFunc("/", handler) 26 | } 27 | 28 | func handler(w http.ResponseWriter, r *http.Request) { 29 | // first create a new context 30 | c := appengine.NewContext(r) 31 | // and use that context to create a new http client 32 | client := urlfetch.Client(c) 33 | 34 | // now we can use that http client as before 35 | res, err := client.Get("http://google.com") 36 | if err != nil { 37 | http.Error(w, fmt.Sprintf("could not get google: %v", err), http.StatusInternalServerError) 38 | return 39 | } 40 | fmt.Fprintf(w, "Got Google with status %s\n", res.Status) 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Want to contribute? 2 | 3 | Great! First, read this page (including the small print at the end). 4 | 5 | ### Before you contribute 6 | 7 | Before we can use your code, you must sign the Google Individual Contributor 8 | License Agreement (CLA), which you can do online. The CLA is necessary mainly 9 | because you own the copyright to your changes, even after your contribution 10 | becomes part of our codebase, so we need your permission to use and distribute 11 | your code. We also need to be sure of various other things—for instance that 12 | you'll tell us if you know that your code infringes on other people's patents. 13 | You don't have to sign the CLA until after you've submitted your code for 14 | review and a member has approved it, but you must do it before we can put your 15 | code into our codebase. Before you start working on a larger contribution, you 16 | should get in touch with us first through the issue tracker with your idea so 17 | that we can help out and possibly guide you. Coordinating up front makes it 18 | much easier to avoid frustration later on. 19 | 20 | ### Code reviews 21 | 22 | All submissions, including submissions by project members, require review. 23 | We use Github pull requests for this purpose. 24 | 25 | ### The small print 26 | 27 | Contributions made by corporations are covered by a different agreement than 28 | the one above, the Software Grant and Corporate Contributor License Agreement. 29 | -------------------------------------------------------------------------------- /section03/examples/handlers/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "io/ioutil" 19 | "log" 20 | "net/http" 21 | ) 22 | 23 | func main() { 24 | http.HandleFunc("/body", bodyHandler) 25 | http.HandleFunc("/param", paramHandler) 26 | log.Fatal(http.ListenAndServe(":8080", nil)) 27 | } 28 | 29 | func bodyHandler(w http.ResponseWriter, r *http.Request) { 30 | b, err := ioutil.ReadAll(r.Body) 31 | if err != nil { 32 | fmt.Fprintf(w, "could not read body: %v", err) 33 | return 34 | } 35 | name := string(b) 36 | if name == "" { 37 | name = "friend" 38 | } 39 | fmt.Fprintf(w, "Hello, %s!", name) 40 | } 41 | 42 | func paramHandler(w http.ResponseWriter, r *http.Request) { 43 | name := r.FormValue("name") 44 | if name == "" { 45 | name = "friend" 46 | } 47 | fmt.Fprintf(w, "Hello, %s!", name) 48 | } 49 | -------------------------------------------------------------------------------- /section10/README.md: -------------------------------------------------------------------------------- 1 | # 10: Congratulations! 2 | 3 | You're done! Really!! You're now a cloud gopher! 🎉👏👍🎂👌 4 | 5 | Enjoy this gopher flying above the clouds. 6 | 7 |
8 |
9 | There's many things that you could do now:
10 |
11 | - Celebrate by closing your computer and going for a walk and some fresh air, you deserve it!
12 | - Learn more about [App Engine](https://cloud.google.com/appengine/docs/go/) and its extra features such as:
13 | - sending email with the [mail](https://cloud.google.com/appengine/docs/go/mail/) package, or [sendgrid](https://cloud.google.com/appengine/docs/go/mail/sendgrid), or [mailgun](https://cloud.google.com/appengine/docs/go/mail/mailgun)
14 | - setting up [authentication](https://cloud.google.com/appengine/docs/go/oauth/)
15 | - manipulating images with the [images API](https://cloud.google.com/appengine/docs/go/images/)
16 | - and many, many more
17 | - Tweet about how awesome this workshop was and mention me [@francesc](https://twitter.com/francesc)
18 | - If you like this repo you should put a 🌟 on it
19 | - Build your next application that will make you rich, and then give me a share of it (thanks )
20 | 🤑)
21 | - Submit issues or fixes to this repo (thanks again)
22 | - Enjoy this animated ASCII train 🚅 powered by the [Go playground](https://play.golang.org/p/NOycgN2i6b)
23 |
24 | Thanks for taking the time of going through the workshop. I hope you enjoyed it!
25 |
26 | _Francesc_
27 |
--------------------------------------------------------------------------------
/section08/fetch/fetch.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package fetch
15 |
16 | import (
17 | "io"
18 | "net/http"
19 |
20 | "google.golang.org/appengine"
21 | "google.golang.org/appengine/log"
22 | "google.golang.org/appengine/urlfetch"
23 | )
24 |
25 | func handler(w http.ResponseWriter, r *http.Request) {
26 | ctx := appengine.NewContext(r)
27 |
28 | // create a new HTTP client
29 | c := urlfetch.Client(ctx)
30 |
31 | // and use it to request the Google homepage
32 | res, err := c.Get("https://google.com")
33 | if err != nil {
34 | http.Error(w, err.Error(), http.StatusInternalServerError)
35 | return
36 | }
37 |
38 | // we need to close the body at the end of this function
39 | defer res.Body.Close()
40 |
41 | // then we can dump the whole webpage onto our output
42 | _, err = io.Copy(w, res.Body)
43 | if err != nil {
44 | log.Errorf(ctx, "could not copy the response: %v", err)
45 | }
46 | }
47 |
48 | func init() {
49 | http.HandleFunc("/", handler)
50 | }
51 |
--------------------------------------------------------------------------------
/section02/examples/gorilla.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package main
15 |
16 | import (
17 | "log"
18 | "net/http"
19 |
20 | "github.com/gorilla/mux"
21 | )
22 |
23 | func listProducts(w http.ResponseWriter, r *http.Request) {
24 | // list all products
25 | }
26 |
27 | func addProduct(w http.ResponseWriter, r *http.Request) {
28 | // add a product
29 | }
30 |
31 | func getProduct(w http.ResponseWriter, r *http.Request) {
32 | id := mux.Vars(r)["productID"]
33 | log.Printf("fetching product with ID %q", id)
34 | // get a specific product
35 | }
36 |
37 | func main() {
38 | r := mux.NewRouter()
39 | // match only GET requests on /product/
40 | r.HandleFunc("/product/", listProducts).Methods("GET")
41 |
42 | // match only POST requests on /product/
43 | r.HandleFunc("/product/", addProduct).Methods("POST")
44 |
45 | // match GET regardless of productID
46 | r.HandleFunc("/product/{productID}", getProduct)
47 |
48 | // handle all requests with the Gorilla router.
49 | http.Handle("/", r)
50 | if err := http.ListenAndServe("127.0.0.1:8080", nil); err != nil {
51 | log.Fatal(err)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/events/step0/static/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | function EventsCtrl($scope, $http) {
17 | // The list of events in display.
18 | $scope.events = [];
19 | // The fields in the event creation dialog.
20 | $scope.newEvent = {};
21 |
22 | // Display an error using an alert dialog.
23 | var alertError = function(data, status) {
24 | alert('code ' + status + ': ' + data);
25 | };
26 |
27 | // Fetches all the events from the API.
28 | var fetchEvents = function() {
29 | return $http.get('/api/events').
30 | error(alertError).
31 | success(function(data) { $scope.events = data; });
32 | };
33 |
34 | // Adds a new event throught the API.
35 | $scope.addEvent = function() {
36 | $http.post('/api/events', $scope.newEvent).
37 | error(alertError).
38 | success(function() {
39 | fetchEvents().then(function () {
40 | // If everything worked, clear the dialog.
41 | $scope.event = {};
42 | // Fetch again after a bit, in case of eventual consistency.
43 | setTimeout(fetchEvents, 1000);
44 | });
45 | });
46 | };
47 |
48 | // Fetch the list of events from the API.
49 | fetchEvents();
50 | }
51 |
--------------------------------------------------------------------------------
/events/step1/static/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | function EventsCtrl($scope, $http) {
17 | // The list of events in display.
18 | $scope.events = [];
19 | // The fields in the event creation dialog.
20 | $scope.newEvent = {};
21 |
22 | // Display an error using an alert dialog.
23 | var alertError = function(data, status) {
24 | alert('code ' + status + ': ' + data);
25 | };
26 |
27 | // Fetches all the events from the API.
28 | var fetchEvents = function() {
29 | return $http.get('/api/events').
30 | error(alertError).
31 | success(function(data) { $scope.events = data; });
32 | };
33 |
34 | // Adds a new event throught the API.
35 | $scope.addEvent = function() {
36 | $http.post('/api/events', $scope.newEvent).
37 | error(alertError).
38 | success(function() {
39 | fetchEvents().then(function () {
40 | // If everything worked, clear the dialog.
41 | $scope.event = {};
42 | // Fetch again after a bit, in case of eventual consistency.
43 | setTimeout(fetchEvents, 1000);
44 | });
45 | });
46 | };
47 |
48 | // Fetch the list of events from the API.
49 | fetchEvents();
50 | }
51 |
--------------------------------------------------------------------------------
/events/step2/static/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | function EventsCtrl($scope, $http) {
17 | // The list of events in display.
18 | $scope.events = [];
19 | // The fields in the event creation dialog.
20 | $scope.newEvent = {};
21 |
22 | // Display an error using an alert dialog.
23 | var alertError = function(data, status) {
24 | alert('code ' + status + ': ' + data);
25 | };
26 |
27 | // Fetches all the events from the API.
28 | var fetchEvents = function() {
29 | return $http.get('/api/events').
30 | error(alertError).
31 | success(function(data) { $scope.events = data; });
32 | };
33 |
34 | // Adds a new event throught the API.
35 | $scope.addEvent = function() {
36 | $http.post('/api/events', $scope.newEvent).
37 | error(alertError).
38 | success(function() {
39 | fetchEvents().then(function () {
40 | // If everything worked, clear the dialog.
41 | $scope.event = {};
42 | // Fetch again after a bit, in case of eventual consistency.
43 | setTimeout(fetchEvents, 1000);
44 | });
45 | });
46 | };
47 |
48 | // Fetch the list of events from the API.
49 | fetchEvents();
50 | }
51 |
--------------------------------------------------------------------------------
/events/step3/static/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | function EventsCtrl($scope, $http) {
17 | // The list of events in display.
18 | $scope.events = [];
19 | // The fields in the event creation dialog.
20 | $scope.newEvent = {};
21 |
22 | // Display an error using an alert dialog.
23 | var alertError = function(data, status) {
24 | alert('code ' + status + ': ' + data);
25 | };
26 |
27 | // Fetches all the events from the API.
28 | var fetchEvents = function() {
29 | return $http.get('/api/events').
30 | error(alertError).
31 | success(function(data) { $scope.events = data; });
32 | };
33 |
34 | // Adds a new event throught the API.
35 | $scope.addEvent = function() {
36 | $http.post('/api/events', $scope.newEvent).
37 | error(alertError).
38 | success(function() {
39 | fetchEvents().then(function () {
40 | // If everything worked, clear the dialog.
41 | $scope.event = {};
42 | // Fetch again after a bit, in case of eventual consistency.
43 | setTimeout(fetchEvents, 1000);
44 | });
45 | });
46 | };
47 |
48 | // Fetch the list of events from the API.
49 | fetchEvents();
50 | }
51 |
--------------------------------------------------------------------------------
/events/step4/static/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | function EventsCtrl($scope, $http) {
17 | // The list of events in display.
18 | $scope.events = [];
19 | // The fields in the event creation dialog.
20 | $scope.newEvent = {};
21 |
22 | // Display an error using an alert dialog.
23 | var alertError = function(data, status) {
24 | alert('code ' + status + ': ' + data);
25 | };
26 |
27 | // Fetches all the events from the API.
28 | var fetchEvents = function() {
29 | return $http.get('/api/events').
30 | error(alertError).
31 | success(function(data) { $scope.events = data; });
32 | };
33 |
34 | // Adds a new event throught the API.
35 | $scope.addEvent = function() {
36 | $http.post('/api/events', $scope.newEvent).
37 | error(alertError).
38 | success(function() {
39 | fetchEvents().then(function () {
40 | // If everything worked, clear the dialog.
41 | $scope.event = {};
42 | // Fetch again after a bit, in case of eventual consistency.
43 | setTimeout(fetchEvents, 1000);
44 | });
45 | });
46 | };
47 |
48 | // Fetch the list of events from the API.
49 | fetchEvents();
50 | }
51 |
--------------------------------------------------------------------------------
/events/step5/static/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | function EventsCtrl($scope, $http) {
17 | // The list of events in display.
18 | $scope.events = [];
19 | // The fields in the event creation dialog.
20 | $scope.newEvent = {};
21 |
22 | // Display an error using an alert dialog.
23 | var alertError = function(data, status) {
24 | alert('code ' + status + ': ' + data);
25 | };
26 |
27 | // Fetches all the events from the API.
28 | var fetchEvents = function() {
29 | return $http.get('/api/events').
30 | error(alertError).
31 | success(function(data) { $scope.events = data; });
32 | };
33 |
34 | // Adds a new event throught the API.
35 | $scope.addEvent = function() {
36 | $http.post('/api/events', $scope.newEvent).
37 | error(alertError).
38 | success(function() {
39 | fetchEvents().then(function () {
40 | // If everything worked, clear the dialog.
41 | $scope.event = {};
42 | // Fetch again after a bit, in case of eventual consistency.
43 | setTimeout(fetchEvents, 1000);
44 | });
45 | });
46 | };
47 |
48 | // Fetch the list of events from the API.
49 | fetchEvents();
50 | }
51 |
--------------------------------------------------------------------------------
/section09/getset/app.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | //
7 | // Unless required by applicable law or agreed to writing, software distributed
8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
9 | // CONDITIONS OF ANY KIND, either express or implied.
10 | //
11 | // See the License for the specific language governing permissions and
12 | // limitations under the License.
13 |
14 | package app
15 |
16 | import (
17 | "fmt"
18 | "net/http"
19 | "time"
20 |
21 | "google.golang.org/appengine"
22 | "google.golang.org/appengine/memcache"
23 | )
24 |
25 | func set(w http.ResponseWriter, r *http.Request) {
26 | ctx := appengine.NewContext(r)
27 |
28 | // get the parameters k and v from the request
29 | key := r.FormValue("k")
30 | value := r.FormValue("v")
31 |
32 | item := &memcache.Item{
33 | Key: key,
34 | Value: []byte(value),
35 | Expiration: 1 * time.Hour,
36 | }
37 |
38 | err := memcache.Set(ctx, item)
39 | if err != nil {
40 | http.Error(w, err.Error(), http.StatusInternalServerError)
41 | }
42 | }
43 |
44 | func get(w http.ResponseWriter, r *http.Request) {
45 | ctx := appengine.NewContext(r)
46 |
47 | key := r.FormValue("k")
48 |
49 | item, err := memcache.Get(ctx, key)
50 | switch err {
51 | case nil:
52 | fmt.Fprintf(w, "%s", item.Value)
53 | case memcache.ErrCacheMiss:
54 | fmt.Fprint(w, "key not found")
55 | default:
56 | http.Error(w, err.Error(), http.StatusInternalServerError)
57 | }
58 | }
59 |
60 | func init() {
61 | http.HandleFunc("/get", get)
62 | http.HandleFunc("/set", set)
63 | }
64 |
--------------------------------------------------------------------------------
/events/step0/static/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | html, body {
17 | font-family: 'Roboto', sans-serif;
18 | width: 100%;
19 | padding: 0;
20 | margin: 0;
21 | }
22 |
23 | h1 {
24 | text-align: center;
25 | background: #eee;
26 | padding: 30px;
27 | margin: 0 0 20px 0;
28 | box-shadow: 0 10px 20px #aaa;
29 | }
30 |
31 | .content {
32 | max-width: 800px;
33 | margin: auto;
34 | text-align: center;
35 | }
36 |
37 | .event, form {
38 | display: inline-block;
39 | width: 300px;
40 | box-shadow: 0 0 10px #333;
41 | border-radius: 10px;
42 | margin: 10px;
43 | }
44 |
45 | form {
46 | padding: 10px 0;
47 | background: #333;
48 | }
49 |
50 | form * {
51 | width: 80%;
52 | font-size: 0.8em;
53 | border: none;
54 | margin: 4px 10px;
55 | border-radius: 10px;
56 | text-align: center;
57 | }
58 |
59 | textarea {
60 | resize: vertical;
61 | }
62 |
63 | .title {
64 | display: block;
65 | text-align: center;
66 | margin: 10px;
67 | font-size: 1.5em;
68 | }
69 |
70 | .header {
71 | text-align: center;
72 | font-size: 0.8em;
73 | margin: 0;
74 | }
75 |
76 | .date {
77 | display: none;
78 | }
79 |
80 | .description {
81 | padding: 10px;
82 | line-height: 18px;
83 | }
84 |
--------------------------------------------------------------------------------
/events/step1/static/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | html, body {
17 | font-family: 'Roboto', sans-serif;
18 | width: 100%;
19 | padding: 0;
20 | margin: 0;
21 | }
22 |
23 | h1 {
24 | text-align: center;
25 | background: #eee;
26 | padding: 30px;
27 | margin: 0 0 20px 0;
28 | box-shadow: 0 10px 20px #aaa;
29 | }
30 |
31 | .content {
32 | max-width: 800px;
33 | margin: auto;
34 | text-align: center;
35 | }
36 |
37 | .event, form {
38 | display: inline-block;
39 | width: 300px;
40 | box-shadow: 0 0 10px #333;
41 | border-radius: 10px;
42 | margin: 10px;
43 | }
44 |
45 | form {
46 | padding: 10px 0;
47 | background: #333;
48 | }
49 |
50 | form * {
51 | width: 80%;
52 | font-size: 0.8em;
53 | border: none;
54 | margin: 4px 10px;
55 | border-radius: 10px;
56 | text-align: center;
57 | }
58 |
59 | textarea {
60 | resize: vertical;
61 | }
62 |
63 | .title {
64 | display: block;
65 | text-align: center;
66 | margin: 10px;
67 | font-size: 1.5em;
68 | }
69 |
70 | .header {
71 | text-align: center;
72 | font-size: 0.8em;
73 | margin: 0;
74 | }
75 |
76 | .date {
77 | display: none;
78 | }
79 |
80 | .description {
81 | padding: 10px;
82 | line-height: 18px;
83 | }
84 |
--------------------------------------------------------------------------------
/events/step2/static/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | html, body {
17 | font-family: 'Roboto', sans-serif;
18 | width: 100%;
19 | padding: 0;
20 | margin: 0;
21 | }
22 |
23 | h1 {
24 | text-align: center;
25 | background: #eee;
26 | padding: 30px;
27 | margin: 0 0 20px 0;
28 | box-shadow: 0 10px 20px #aaa;
29 | }
30 |
31 | .content {
32 | max-width: 800px;
33 | margin: auto;
34 | text-align: center;
35 | }
36 |
37 | .event, form {
38 | display: inline-block;
39 | width: 300px;
40 | box-shadow: 0 0 10px #333;
41 | border-radius: 10px;
42 | margin: 10px;
43 | }
44 |
45 | form {
46 | padding: 10px 0;
47 | background: #333;
48 | }
49 |
50 | form * {
51 | width: 80%;
52 | font-size: 0.8em;
53 | border: none;
54 | margin: 4px 10px;
55 | border-radius: 10px;
56 | text-align: center;
57 | }
58 |
59 | textarea {
60 | resize: vertical;
61 | }
62 |
63 | .title {
64 | display: block;
65 | text-align: center;
66 | margin: 10px;
67 | font-size: 1.5em;
68 | }
69 |
70 | .header {
71 | text-align: center;
72 | font-size: 0.8em;
73 | margin: 0;
74 | }
75 |
76 | .date {
77 | display: none;
78 | }
79 |
80 | .description {
81 | padding: 10px;
82 | line-height: 18px;
83 | }
84 |
--------------------------------------------------------------------------------
/events/step3/static/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | html, body {
17 | font-family: 'Roboto', sans-serif;
18 | width: 100%;
19 | padding: 0;
20 | margin: 0;
21 | }
22 |
23 | h1 {
24 | text-align: center;
25 | background: #eee;
26 | padding: 30px;
27 | margin: 0 0 20px 0;
28 | box-shadow: 0 10px 20px #aaa;
29 | }
30 |
31 | .content {
32 | max-width: 800px;
33 | margin: auto;
34 | text-align: center;
35 | }
36 |
37 | .event, form {
38 | display: inline-block;
39 | width: 300px;
40 | box-shadow: 0 0 10px #333;
41 | border-radius: 10px;
42 | margin: 10px;
43 | }
44 |
45 | form {
46 | padding: 10px 0;
47 | background: #333;
48 | }
49 |
50 | form * {
51 | width: 80%;
52 | font-size: 0.8em;
53 | border: none;
54 | margin: 4px 10px;
55 | border-radius: 10px;
56 | text-align: center;
57 | }
58 |
59 | textarea {
60 | resize: vertical;
61 | }
62 |
63 | .title {
64 | display: block;
65 | text-align: center;
66 | margin: 10px;
67 | font-size: 1.5em;
68 | }
69 |
70 | .header {
71 | text-align: center;
72 | font-size: 0.8em;
73 | margin: 0;
74 | }
75 |
76 | .date {
77 | display: none;
78 | }
79 |
80 | .description {
81 | padding: 10px;
82 | line-height: 18px;
83 | }
84 |
--------------------------------------------------------------------------------
/events/step4/static/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | html, body {
17 | font-family: 'Roboto', sans-serif;
18 | width: 100%;
19 | padding: 0;
20 | margin: 0;
21 | }
22 |
23 | h1 {
24 | text-align: center;
25 | background: #eee;
26 | padding: 30px;
27 | margin: 0 0 20px 0;
28 | box-shadow: 0 10px 20px #aaa;
29 | }
30 |
31 | .content {
32 | max-width: 800px;
33 | margin: auto;
34 | text-align: center;
35 | }
36 |
37 | .event, form {
38 | display: inline-block;
39 | width: 300px;
40 | box-shadow: 0 0 10px #333;
41 | border-radius: 10px;
42 | margin: 10px;
43 | }
44 |
45 | form {
46 | padding: 10px 0;
47 | background: #333;
48 | }
49 |
50 | form * {
51 | width: 80%;
52 | font-size: 0.8em;
53 | border: none;
54 | margin: 4px 10px;
55 | border-radius: 10px;
56 | text-align: center;
57 | }
58 |
59 | textarea {
60 | resize: vertical;
61 | }
62 |
63 | .title {
64 | display: block;
65 | text-align: center;
66 | margin: 10px;
67 | font-size: 1.5em;
68 | }
69 |
70 | .header {
71 | text-align: center;
72 | font-size: 0.8em;
73 | margin: 0;
74 | }
75 |
76 | .date {
77 | display: none;
78 | }
79 |
80 | .description {
81 | padding: 10px;
82 | line-height: 18px;
83 | }
84 |
--------------------------------------------------------------------------------
/events/step5/static/style.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Google Inc. All rights reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to writing, software distributed
9 | * under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
10 | * CONDITIONS OF ANY KIND, either express or implied.
11 | *
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | html, body {
17 | font-family: 'Roboto', sans-serif;
18 | width: 100%;
19 | padding: 0;
20 | margin: 0;
21 | }
22 |
23 | h1 {
24 | text-align: center;
25 | background: #eee;
26 | padding: 30px;
27 | margin: 0 0 20px 0;
28 | box-shadow: 0 10px 20px #aaa;
29 | }
30 |
31 | .content {
32 | max-width: 800px;
33 | margin: auto;
34 | text-align: center;
35 | }
36 |
37 | .event, form {
38 | display: inline-block;
39 | width: 300px;
40 | box-shadow: 0 0 10px #333;
41 | border-radius: 10px;
42 | margin: 10px;
43 | }
44 |
45 | form {
46 | padding: 10px 0;
47 | background: #333;
48 | }
49 |
50 | form * {
51 | width: 80%;
52 | font-size: 0.8em;
53 | border: none;
54 | margin: 4px 10px;
55 | border-radius: 10px;
56 | text-align: center;
57 | }
58 |
59 | textarea {
60 | resize: vertical;
61 | }
62 |
63 | .title {
64 | display: block;
65 | text-align: center;
66 | margin: 10px;
67 | font-size: 1.5em;
68 | }
69 |
70 | .header {
71 | text-align: center;
72 | font-size: 0.8em;
73 | margin: 0;
74 | }
75 |
76 | .date {
77 | display: none;
78 | }
79 |
80 | .description {
81 | padding: 10px;
82 | line-height: 18px;
83 | }
84 |
--------------------------------------------------------------------------------
/events/step0/static/index.html:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |{{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |{{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |{{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |{{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |{{e.weather}} in {{e.location}}
37 | {{e.date}} 38 |{{e.description}}
39 |The backend says:
133 | 138 | 139 | 140 | ``` 141 | 142 | ## Directory organization for bigger applications 143 | 144 | As you can imagine very soon you will want to serve many static files, some 145 | HTML, JavaScript, CSS, etc. Rather than listing each of those on your `app.yaml` 146 | there's a much simpler option once you put all of them in a single directory. 147 | 148 | my_app 149 | |- app.yaml 150 | |- hello.go 151 | \- static 152 | |- index.html 153 | |- style.css 154 | \- script.js 155 | 156 | Your `app.yaml` in this case will look like this: 157 | 158 | [embedmd]:# (static_dirs/app.yaml /runtime/ $) 159 | ```yaml 160 | runtime: go 161 | api_version: go1 162 | 163 | handlers: 164 | # requests starting with /api/ are handled by the Go app. 165 | - url: /api/.* 166 | script: _go_app 167 | 168 | # if the path is empty show index.html. 169 | - url: / 170 | static_files: static/index.html 171 | upload: static/index.html 172 | 173 | # otherwise try to find it in the static directory. 174 | - url: / 175 | static_dir: static 176 | ``` 177 | 178 | This kind of structure provides a clear structure of the application. 179 | 180 | ## Exercise: let's build a whole app! 181 | 182 | OK, we know now enough stuff to build an application. So, let's do it! 🎉 183 | 184 | Have a look at this [introduction](../events) describing the application 185 | and implement *ONLY* [step 0](../events/step0/README.md). Then come back for more. 186 | 187 | # Congratulations! 188 | 189 | You just created your first application where a web frontend communicates with 190 | your Go backend! 191 | 192 | But, shouldn't the backend generate JSON rather than plain text? 193 | Let's learn about JSON encoding and decoding on the [next section](../section06/README.md). 194 | -------------------------------------------------------------------------------- /section01/README.md: -------------------------------------------------------------------------------- 1 | # 1: Web Clients 2 | 3 | In this section you're going to learn everything you need to know to write a web servers and clients in Go. 4 | 5 | The `net/http` package provides a series of functions and types to help you sending HTTP requests. 6 | The most important types are: 7 | 8 | - the [Client](https://golang.org/pkg/net/http#Client) 9 | - the [Request](https://golang.org/pkg/net/http#Request) 10 | - the [Response](https://golang.org/pkg/net/http#Response) 11 | 12 | We'll get to see how those types work in a minute. 13 | But before that it is important to realize that there's helper functions that will make our life easier here too. 14 | 15 | ## The Get method 16 | 17 | One of those helper functions is [`Get`](https://golang.org/pkg/net/http#Get). 18 | 19 | [embedmd]:# (examples/get.go /package main/ $) 20 | ```go 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "log" 26 | "net/http" 27 | ) 28 | 29 | func main() { 30 | // try changing the value of this url 31 | res, err := http.Get("https://golang.org") 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | fmt.Println(res.Status) 36 | } 37 | ``` 38 | [source code](examples/get.go) 39 | 40 | This program will send a GET HTTP request to the Go homepage and will print the status code of the response, 41 | unless something goes wrong in which case it will log the error and stop the execution of the program. 42 | 43 | Try changing the value of the URL to see what other codes you're able to get. 44 | Some ideas you could try: 45 | 46 | - https://golang.org/foo 47 | - https://thisurldoesntexist.com 48 | - https:/thisisnotaurl 49 | 50 | ### Exercise status codes 51 | 52 | The `Get` function we just used returns a `Response` and an `error`. 53 | Read the documentation of `Response` and modify `get.go` so it prints a message only when the status code of the response is 404. 54 | 55 | ### Exercise body 56 | 57 | The `Response` type also has a `Body` field of type `io.ReadCloser`. 58 | The type of this field is a big hint on what we can do with it: read it and close it. 59 | 60 | Modify the program `get.go` so it will also print the body of the `Response` onto the standard output. 61 | Remember that the `Body` should be closed at the end of your program to avoid leaking memory! 62 | 63 | ## Other methods 64 | 65 | The `Get` method we just used is a helper function that calls the `Get` method of the `http.DefaultClient`. 66 | The `Client` type offers some other methods related directly to the HTTP methods we all know: 67 | 68 | - [Client.Get](https://golang.org/pkg/net/http#Client.Get) 69 | - [Client.Post](https://golang.org/pkg/net/http#Client.Post) 70 | - [Client.PostForm](https://golang.org/pkg/net/http#Client.PostForm) 71 | - [Client.Head](https://golang.org/pkg/net/http#Client.Head) 72 | 73 | and the equivalent helper functions: 74 | 75 | - [http.Get](https://golang.org/pkg/net/http#Get) 76 | - [http.Post](https://golang.org/pkg/net/http#Post) 77 | - [http.PostForm](https://golang.org/pkg/net/http#PostForm) 78 | - [http.Head](https://golang.org/pkg/net/http#Head) 79 | 80 | What if you want to use some other methods? Meet the [`Do`](https://golang.org/pkg/net/http#Client.Do) method. 81 | This method receives a pointer to `http.Request` as parameters and return a `http.Response` and an `error`. 82 | 83 | The `Request` method provides all the expressivity we need to send any kind of HTTP requests. 84 | 85 | For instance, we can create the equivalent request to the original `get.go` program: 86 | 87 | The `Client` type exposes the `Do` method that send the given `Request` and returns a `Response` and an `error`. 88 | 89 | [embedmd]:# (examples/do-get.go /package main/ $) 90 | ```go 91 | package main 92 | 93 | import ( 94 | "fmt" 95 | "log" 96 | "net/http" 97 | ) 98 | 99 | func doGet() { 100 | req, err := http.NewRequest("GET", "https://golang.org", nil) 101 | if err != nil { 102 | log.Fatalf("could not create request: %v", err) 103 | } 104 | client := http.DefaultClient 105 | res, err := client.Do(req) 106 | if err != nil { 107 | log.Fatalf("http request failed: %v", err) 108 | } 109 | fmt.Println(res.Status) 110 | } 111 | ``` 112 | [source code](examples/do-get.go) 113 | 114 | This also allows us to provide a `Body` for the `Request`, similar to the one we had with the `Response`. 115 | The big question is how to create an `io.Reader`? 116 | 117 | Well, it depends on the type of what you want to read, but if you want to provide a `string` it is quite easy. 118 | You can create an `io.Reader` from a `string` with [`strings.NewReader`](https://golang.org/pkg/strings#NewReader). 119 | 120 | ### Exercise PUT request 121 | 122 | Modify the code above to send a PUT request to https://http-methods.appspot.com/YourName/Message. 123 | Replace `YourName` with your name, or something unique that no one else might be using. 124 | This will save whatever string you send in the Body of the request so you can retrieve it later with: 125 | 126 | ``` 127 | $ curl https://http-methods.appspot.com/YourName/Message 128 | ``` 129 | 130 | ## Parameters: queries and forms 131 | 132 | The web application behind https://http-methods.appspot.com supports printing all the keys under a namespace. 133 | Try visiting https://http-methods.appspot.com/Hungary/ and you'll see all the keys in that namespace. 134 | To also show the values you can add `?v=true` to the url: https://http-methods.appspot.com/Hungary/?v=true . 135 | 136 | ### Exercise queries 137 | 138 | Write a program that will fetch and display all the keys and values in a namespace. 139 | Rather than adding the `?v=true` at the end of the URL passed to `NewRequest` find the way to add it 140 | directly in the `Request` that you pass to `Do`. 141 | 142 | # Congratulations! 143 | 144 | Good job! You now know pretty much everything there is to know (today) for HTTP clients, requests, and responses. 145 | Now, to understand how all of this is handled on the server side, let's go the [next section](../section02/README.md). 146 | -------------------------------------------------------------------------------- /utils/http-methods/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google Inc. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // http://www.apache.org/licenses/LICENSE-2.0 6 | // 7 | // Unless required by applicable law or agreed to writing, software distributed 8 | // under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | // CONDITIONS OF ANY KIND, either express or implied. 10 | // 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package app 15 | 16 | import ( 17 | "fmt" 18 | "io/ioutil" 19 | "net/http" 20 | 21 | "google.golang.org/appengine" 22 | "google.golang.org/appengine/datastore" 23 | "google.golang.org/appengine/log" 24 | 25 | "github.com/gorilla/mux" 26 | ) 27 | 28 | const ( 29 | namespaceKind = "namespace" 30 | valueKind = "value" 31 | ) 32 | 33 | type value struct{ Value string } 34 | 35 | func init() { 36 | r := mux.NewRouter() 37 | r.Handle("/", csrfHandler{http.HandlerFunc(getNamespaces)}).Methods("GET") 38 | r.Handle("/{namespace}/", csrfHandler{withNamespace{getAll, false}}).Methods("GET") 39 | r.Handle("/{namespace}/{key}", csrfHandler{withNamespace{getOne, false}}).Methods("GET") 40 | r.Handle("/{namespace}/{key}", csrfHandler{withNamespace{put, true}}).Methods("PUT") 41 | r.Handle("/{namespace}/{key}", csrfHandler{withNamespace{delete, false}}).Methods("DELETE") 42 | http.Handle("/", r) 43 | } 44 | 45 | func getNamespaces(w http.ResponseWriter, r *http.Request) { 46 | ctx := appengine.NewContext(r) 47 | log.Infof(ctx, "getNamespaces") 48 | 49 | keys, err := datastore.NewQuery(namespaceKind).KeysOnly().GetAll(ctx, nil) 50 | if err != nil { 51 | http.Error(w, err.Error(), http.StatusInternalServerError) 52 | return 53 | } 54 | 55 | w.Header().Add("Content-Type", "text/plain") 56 | for _, key := range keys { 57 | fmt.Fprintln(w, key.StringID()) 58 | } 59 | } 60 | 61 | type csrfHandler struct{ h http.Handler } 62 | 63 | func (h csrfHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 64 | w.Header().Set("Access-Control-Allow-Origin", "*") 65 | h.h.ServeHTTP(w, r) 66 | } 67 | 68 | type withNamespace struct { 69 | h func(w http.ResponseWriter, r *http.Request, namespace *datastore.Key) 70 | 71 | createIfMissing bool 72 | } 73 | 74 | func (h withNamespace) ServeHTTP(w http.ResponseWriter, r *http.Request) { 75 | ctx := appengine.NewContext(r) 76 | 77 | namespace := mux.Vars(r)["namespace"] 78 | key := datastore.NewKey(ctx, namespaceKind, namespace, 0, nil) 79 | if err := datastore.Get(ctx, key, new(struct{})); err == datastore.ErrNoSuchEntity { 80 | if !h.createIfMissing { 81 | http.Error(w, fmt.Sprintf("namespace %s not found", namespace), http.StatusNotFound) 82 | return 83 | } 84 | 85 | _, err := datastore.Put(ctx, key, new(struct{})) 86 | if err != nil { 87 | http.Error(w, fmt.Sprintf("could not create namespace %s: %v", namespace, err), http.StatusInternalServerError) 88 | return 89 | } 90 | } else if err != nil { 91 | http.Error(w, fmt.Sprintf("fetching namespace %s: %v", namespace, err), http.StatusInternalServerError) 92 | return 93 | } 94 | h.h(w, r, key) 95 | } 96 | 97 | func getAll(w http.ResponseWriter, r *http.Request, namespace *datastore.Key) { 98 | ctx := appengine.NewContext(r) 99 | log.Infof(ctx, "getAll") 100 | 101 | values := []value{} 102 | keys, err := datastore.NewQuery(valueKind).Ancestor(namespace).GetAll(ctx, &values) 103 | if err != nil { 104 | http.Error(w, err.Error(), http.StatusInternalServerError) 105 | return 106 | } 107 | 108 | w.Header().Add("Content-Type", "text/plain") 109 | printValues := r.FormValue("v") == "true" 110 | if printValues { 111 | for i, key := range keys { 112 | fmt.Fprintf(w, "%s:%s\n", key.StringID(), values[i].Value) 113 | } 114 | } else { 115 | for _, key := range keys { 116 | fmt.Fprintln(w, key.StringID()) 117 | } 118 | } 119 | } 120 | 121 | func getOne(w http.ResponseWriter, r *http.Request, namespace *datastore.Key) { 122 | ctx := appengine.NewContext(r) 123 | log.Infof(ctx, "getOne") 124 | 125 | vars := mux.Vars(r) 126 | keyName := vars["key"] 127 | key := datastore.NewKey(ctx, valueKind, keyName, 0, namespace) 128 | 129 | var v value 130 | if err := datastore.Get(ctx, key, &v); err == datastore.ErrNoSuchEntity { 131 | http.Error(w, fmt.Sprintf("key %s not found in namespace %v", keyName, namespace.StringID()), http.StatusNotFound) 132 | return 133 | } else if err != nil { 134 | http.Error(w, fmt.Sprintf("fetching value %s in %s: %v", keyName, namespace.StringID(), err), http.StatusInternalServerError) 135 | return 136 | } 137 | 138 | w.Header().Add("Content-Type", "text/plain") 139 | fmt.Fprintln(w, v.Value) 140 | } 141 | 142 | func put(w http.ResponseWriter, r *http.Request, namespace *datastore.Key) { 143 | ctx := appengine.NewContext(r) 144 | log.Infof(ctx, "put") 145 | 146 | vars := mux.Vars(r) 147 | keyName := vars["key"] 148 | key := datastore.NewKey(ctx, valueKind, keyName, 0, namespace) 149 | 150 | b, err := ioutil.ReadAll(r.Body) 151 | if err != nil { 152 | http.Error(w, err.Error(), http.StatusInternalServerError) 153 | return 154 | } 155 | v := value{string(b)} 156 | 157 | if _, err := datastore.Put(ctx, key, &v); err != nil { 158 | http.Error(w, err.Error(), http.StatusInternalServerError) 159 | } 160 | } 161 | 162 | func delete(w http.ResponseWriter, r *http.Request, namespace *datastore.Key) { 163 | ctx := appengine.NewContext(r) 164 | log.Infof(ctx, "delete") 165 | 166 | vars := mux.Vars(r) 167 | keyName := vars["key"] 168 | 169 | key := datastore.NewKey(ctx, valueKind, keyName, 0, namespace) 170 | if err := datastore.Delete(ctx, key); err != nil { 171 | http.Error(w, fmt.Sprintf("fetching value %s in %s: %v", keyName, namespace, err), http.StatusInternalServerError) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /section06/README.md: -------------------------------------------------------------------------------- 1 | # 6: JSON encoding and decoding 2 | 3 | Go's standard library provides JSON encoding and decoding with the package 4 | [`encoding/json`](https://golang.org/pkg/encoding/json). 5 | 6 | ## Encoding and decoding JSON 7 | 8 | Let's first learn how to encode and decode JSON in a general way and we'll 9 | see afterwards how to do it inside of an HTTP server. 10 | 11 | ### JSON and Go structs 12 | 13 | The easiest way to encode and decode JSON objects with Go is to create a Go type 14 | which matches the structure of the JSON object we want to decode. 15 | 16 | So given a JSON object like this: 17 | 18 | ```json 19 | { 20 | "name": "gopher", 21 | "age_years": 5 22 | } 23 | ``` 24 | 25 | We would create a type containing the same fields: 26 | 27 | ```go 28 | type Person struct { 29 | Name string 30 | AgeYears int 31 | } 32 | ``` 33 | 34 | Note that the all the identifiers (both type and fields) start with an uppercase 35 | letter. This is because only identifiers starting with an uppercase are exported 36 | outside of a package. So if the field `Name` was `name` the `encoding/json` 37 | package wouldn't be able to even know it is there. 38 | 39 | Fortunately we can use field tags to modify what name is used in the JSON form 40 | for each Go field. 41 | 42 | For instance we would add the following field tags to the previous example: 43 | 44 | [embedmd]:# (examples/app.go /type Person/ /^}/) 45 | ```go 46 | type Person struct { 47 | Name string `json:"name"` 48 | AgeYears int `json:"age_years"` 49 | } 50 | ``` 51 | 52 | _Note_: the backticks ```(`)``` are just a different way to write strings in Go. 53 | They allow you to use double quotes `(")` and to expand across multiple lines. 54 | 55 | For more info on structs read 56 | [this section](https://tour.golang.org/moretypes/5) of the Go tour. 57 | 58 | ### Encoding Go structs to JSON 59 | 60 | To encode a Go struct we use a 61 | [`json.Encoder`](https://golang.org/pkg/encoding/json#Encoder), which provides 62 | a handy `Encode` method. 63 | 64 | [embedmd]:# (examples/app.go /func encode/ /^}/) 65 | ```go 66 | func encode() { 67 | p := Person{"gopher", 5} 68 | 69 | // create an encoder that will write on the standard output. 70 | enc := json.NewEncoder(os.Stdout) 71 | // use the encoder to encode p, which could fail. 72 | err := enc.Encode(p) 73 | // if it failed, log the error and stop execution. 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | } 78 | ``` 79 | 80 | This code snippet shows how to handle errors every time we encode a value, 81 | and while in the example it seems impossible to have an error consider that 82 | the encoder output could be sent through a network connection. 83 | 84 | You can try the code with the `go run` tool, or using the Go playground 85 | [here](https://play.golang.org/p/rsO0Vk-9Xl). 86 | 87 | ### Decoding JSON objects into Go structs 88 | 89 | The same way we have a `json.Encoder` we have a `json.Decoder` and its usage 90 | is very similar. 91 | 92 | [embedmd]:# (examples/app.go /func decode/ /^}/) 93 | ```go 94 | func decode() { 95 | // create an empty Person value. 96 | var p Person 97 | 98 | // create a decoder reading from the standard input. 99 | dec := json.NewDecoder(os.Stdin) 100 | // use the decoder to decode a value into p. 101 | err := dec.Decode(&p) 102 | // if it failed, log the error and stop execution. 103 | if err != nil { 104 | log.Fatal(err) 105 | } 106 | // otherwise log what we decoded. 107 | fmt.Printf("decoded: %#v\n", p) 108 | } 109 | ``` 110 | 111 | Note that the parameter for `dec.Decode` is not `p` but `&p`. This is a 112 | pointer to the variable `p` so the `encoding/json` package can modify the 113 | value of `p`. Otherwise we would pass a copy of `p` and any modifications 114 | would be without side effects. 115 | 116 | Read more about pointers in the [Go tour](https://tour.golang.org/moretypes/1). 117 | 118 | ## encoding/json + net/http = web services! 119 | 120 | Let's have another look at the `http.HandlerFunc` type: 121 | 122 | ```go 123 | type HandlerFunc func(ResponseWriter, *Request) 124 | ``` 125 | 126 | ### Encoding JSON onto a http.ResponseWriter 127 | 128 | As we mentioned before `http.ResponseWriter` implements the method `Write` and 129 | therefore satisfies the `io.Writer` interface required by `json.NewEncoder`. 130 | 131 | So we can easily JSON encode a `Person` on an HTTP response: 132 | 133 | [embedmd]:# (examples/app.go /func encodeHandler/ /^}/) 134 | ```go 135 | func encodeHandler(w http.ResponseWriter, r *http.Request) { 136 | p := Person{"gopher", 5} 137 | 138 | // set the Content-Type header. 139 | w.Header().Set("Content-Type", "application/json") 140 | 141 | // encode p to the output. 142 | enc := json.NewEncoder(w) 143 | err := enc.Encode(p) 144 | if err != nil { 145 | // if encoding fails, create an error page with code 500. 146 | http.Error(w, err.Error(), http.StatusInternalServerError) 147 | } 148 | } 149 | ``` 150 | 151 | ### Decoding JSON from a http.Request 152 | 153 | The `http.Request` type is a struct and it has a field named `Body` of type 154 | `io.ReadCloser`, an interface with the methods `Read` and `Close`. 155 | 156 | Since the signature of the method `Read` matches the one in `io.Reader` we can 157 | say that `io.ReadCloser` is an `io.Reader` and therefore we can use the `Body` 158 | of a `http.Request` as the input of a `json.Decoder`. 159 | 160 | [embedmd]:# (examples/app.go /func decodeHandler/ /^}/) 161 | ```go 162 | func decodeHandler(w http.ResponseWriter, r *http.Request) { 163 | var p Person 164 | 165 | dec := json.NewDecoder(r.Body) 166 | err := dec.Decode(&p) 167 | if err != nil { 168 | http.Error(w, err.Error(), http.StatusBadRequest) 169 | return 170 | } 171 | fmt.Fprintf(w, "Name is %v and age is %v", p.Name, p.AgeYears) 172 | } 173 | ``` 174 | 175 | If you want to test this handler you can use curl: 176 | 177 | ```bash 178 | $ curl -d '{"name": "gopher", "age_years": 5}' http://localhost:8080/ 179 | Name is gopher and age is 5 180 | ``` 181 | 182 | ## Exercise 183 | 184 | Add JSON encoding and decoding to the events application with [step 1](../events/step1/README.md). 185 | Then come back here for more! 186 | 187 | # Congratulations! 188 | 189 | You've successfully built a web application where the backend and the frontend 190 | interact via JSON messages over HTTP requests: that's pretty much as RESTful as 191 | it gets! 192 | 193 | But what if we want to store some of that information we're decoding? 194 | 195 | Continue to the [next chapter](../section07/README.md) to learn about 196 | the Google Cloud Datastore. 197 | -------------------------------------------------------------------------------- /section09/README.md: -------------------------------------------------------------------------------- 1 | # 9: What is Memcache and how to use it from App Engine 2 | 3 | Memcache is one of the most well known caching system, developed originally by 4 | [Brad Fitzpatrick](https://twitter.com/bradfitz), a current member of the Go team! 5 | 6 | App Engine provides a way an incredibly simple way to use Memcache, similarly to 7 | the way you can use Datastore. You get access to completely managed Memcache 8 | instances that are maintained, scaled, and updated by Google so you don't need to 9 | care about anything other than your business. 10 | 11 | Memcache will keep your data for a period of time you can specify and will drop 12 | the values once a given time has passed. You should not store anything that 13 | you can't recover in Memcache, as there's no durability warranty. In other 14 | words, if Memcache was not working your application should still work perfectly, 15 | even though it would probably be a bit slower. 16 | 17 | ## Using it from the Go App Engine runtime 18 | 19 | As for the packages seen before there's a package provided by the Go runtime 20 | named [`google.golang.org/appengine/memcache`](https://godoc.org/google.golang.org/appengine/memcache). 21 | 22 | There's two main operations you can do with memcache: 23 | 24 | - caching data with `memcache.Set`, and 25 | - retrieve data back with `memcache.Get`. 26 | 27 | ### Caching data 28 | 29 | We will use the `memcache.Set` function: 30 | 31 | ```go 32 | func Set(c appengine.Context, item *Item) error 33 | ``` 34 | 35 | The first parameter is an `appengine.Context`, as usual, and the second one is 36 | the item we want to store. Let's concentrate on three fields of the 37 | `memcache.Item` type for now: 38 | 39 | ```go 40 | type Item struct { 41 | Key string 42 | Value []byte 43 | Expiration time.Duration 44 | 45 | // I removed some fields for clarity here 46 | } 47 | ``` 48 | 49 | - Keys, which are simple strings, let you identify values. 50 | - Values are a slice of bytes of up to 1Mb in size. 51 | - The Expiration fields indicates for how long the value is valid. 52 | 53 | So if we want to store an item with key "name" and value "gopher" that will be 54 | valid for one hour we could write: 55 | 56 | [embedmd]:# (getset/app.go /package app/ /^}/) 57 | ```go 58 | package app 59 | 60 | import ( 61 | "fmt" 62 | "net/http" 63 | "time" 64 | 65 | "google.golang.org/appengine" 66 | "google.golang.org/appengine/memcache" 67 | ) 68 | 69 | func set(w http.ResponseWriter, r *http.Request) { 70 | ctx := appengine.NewContext(r) 71 | 72 | // get the parameters k and v from the request 73 | key := r.FormValue("k") 74 | value := r.FormValue("v") 75 | 76 | item := &memcache.Item{ 77 | Key: key, 78 | Value: []byte(value), 79 | Expiration: 1 * time.Hour, 80 | } 81 | 82 | err := memcache.Set(ctx, item) 83 | if err != nil { 84 | http.Error(w, err.Error(), http.StatusInternalServerError) 85 | } 86 | } 87 | ``` 88 | 89 | ### Retrieving cache data 90 | 91 | To retrieve an item from Memcache we use the `memcache.Get` function: 92 | 93 | ```go 94 | func Get(c appengine.Context, key string) (*Item, error) 95 | ``` 96 | 97 | Given a key `memcache.Get` returns the corresponding item if any, containing the 98 | store value in the `Value` field. If the key was not found in the cache the 99 | returned error is `memcache.ErrCacheMiss`. 100 | 101 | Let's retrieve the value we cached on the previous code: 102 | 103 | [embedmd]:# (getset/app.go /func get/ /^}/) 104 | ```go 105 | func get(w http.ResponseWriter, r *http.Request) { 106 | ctx := appengine.NewContext(r) 107 | 108 | key := r.FormValue("k") 109 | 110 | item, err := memcache.Get(ctx, key) 111 | switch err { 112 | case nil: 113 | fmt.Fprintf(w, "%s", item.Value) 114 | case memcache.ErrCacheMiss: 115 | fmt.Fprint(w, "key not found") 116 | default: 117 | http.Error(w, err.Error(), http.StatusInternalServerError) 118 | } 119 | } 120 | ``` 121 | 122 | You can see a complete example of an application using Memcache [here](getset). 123 | 124 | Test it with `curl` or [Postman](https://chrome.google.com/webstore/detail/fdmmgilgnpjigdojojpjoooidkmcomcm?utm_source=chrome-ntp-launcher). 125 | 126 | ## Using Codecs 127 | 128 | As you can imagine using Memcache to store structure data this way can be a 129 | little bit of a hassle, that's why the package also offers a way to use 130 | coders and encoders, aka codecs, such as JSON. 131 | 132 | When using codecs rather than setting the `Value` field of the `memcache.Item` 133 | you should use the `Object` field instead. For instance you can cache and 134 | retrieve a `Person` using the JSON codec like this: 135 | 136 | [embedmd]:# (codec/app.go /func set/ /^}/) 137 | ```go 138 | func set(w http.ResponseWriter, r *http.Request) { 139 | ctx := appengine.NewContext(r) 140 | 141 | var p Person 142 | if err := json.NewDecoder(r.Body).Decode(&p); err != nil { 143 | http.Error(w, err.Error(), http.StatusBadRequest) 144 | return 145 | } 146 | 147 | item := &memcache.Item{ 148 | Key: "last_person", 149 | Object: p, // we set the Object field instead of Value 150 | Expiration: 1 * time.Hour, 151 | } 152 | 153 | // we use the JSON codec 154 | err := memcache.JSON.Set(ctx, item) 155 | if err != nil { 156 | http.Error(w, err.Error(), http.StatusInternalServerError) 157 | } 158 | } 159 | ``` 160 | 161 | And to retrieve it it's even simpler: 162 | 163 | [embedmd]:# (codec/app.go /func get/ /^}/) 164 | ```go 165 | func get(w http.ResponseWriter, r *http.Request) { 166 | ctx := appengine.NewContext(r) 167 | 168 | var p Person 169 | _, err := memcache.JSON.Get(ctx, "last_person", &p) 170 | if err == nil { 171 | json.NewEncoder(w).Encode(p) 172 | return 173 | } 174 | if err == memcache.ErrCacheMiss { 175 | fmt.Fprint(w, "key not found") 176 | return 177 | } 178 | http.Error(w, err.Error(), http.StatusInternalServerError) 179 | } 180 | ``` 181 | 182 | You can see a complete example of an application using the JSON code 183 | [here](codec). 184 | 185 | ## The Memcache viewer page 186 | 187 | Similarly to the Datastore admin page, you can check some statistics and find 188 | what keys are stored or used in a Memcache instance accessing the Memcache 189 | viewer page. 190 | 191 | If you're running your application locally you can access 192 | http://localhost:8000/memcache: 193 | 194 |
195 |
196 | If you want to view the statistics on a production instance you can use the
197 | Developers Console
198 | [here](https://console.developers.google.com/project/_/appengine/memcache):
199 |
200 |
201 |
202 | # Exercise: make the events app faster with memcache
203 |
204 | Remember the events app? Remember how it spent most of its time fetching weather
205 | information from the openweathermap.org API? Let's fix that with
206 | [step 4](../events/step4/README.md).
207 |
208 | # Congratulations!
209 |
210 | You're now not only able to cache and retrieve data in Memcache, but also to
211 | do so with structured data thanks to the JSON codec!
212 |
213 | Go to the [next section](../section10/README.md) for a couple of surprises!
214 |
--------------------------------------------------------------------------------
/section04/README.md:
--------------------------------------------------------------------------------
1 | # 4: Deploying to App Engine
2 |
3 | In the previous chapter we learned how to write a simple HTTP server in Go as a
4 | static binary that you can execute easily on any server with the same platform.
5 |
6 | ## Getting ready for success
7 |
8 | What would happen if your "Hello, web" went viral? Would your simple binary
9 | handle the load? You would probably need to add some extra servers, which is
10 | probably a good idea just to have redundancy.
11 |
12 | How do you that? Many solutions, but the simplest one is Google App Engine.
13 |
14 |
16 |
17 |
18 |
19 |
20 |
14 |
157 |
158 | If you want to investigate the content of the Cloud Datastore for an App Engine
159 | app deployed to production you can visit
160 | [this page](https://console.developers.google.com/project/_/datastore/stats).
161 |
162 |
163 | # Retrieving data from Google Cloud Datastore
164 |
165 | There are two main ways of retrieving data from the Google Cloud Datastore:
166 |
167 | - retrieving values given their keys, or
168 | - querying the datastore using filters.
169 |
170 | ## Retrieving with keys
171 |
172 | If we have a key, retrieving a value from the Datastore is as simple as calling
173 | the `datastore.Get` function:
174 |
175 | ```go
176 | func Get(c appengine.Context, key *Key, dst interface{}) error
177 | ```
178 |
179 | The last parameter should be a pointer to a struct containing the fields that we
180 | want to retrieve, for instance:
181 |
182 | [embedmd]:# (examples/app.go /func getHandler/ /^}/)
183 | ```go
184 | func getHandler(w http.ResponseWriter, r *http.Request) {
185 | ctx := appengine.NewContext(r)
186 |
187 | key := datastore.NewKey(ctx, "Person", "gopher", 0, nil)
188 |
189 | var p Person
190 | err := datastore.Get(ctx, key, &p)
191 | if err != nil {
192 | http.Error(w, "Person not found", http.StatusNotFound)
193 | return
194 | }
195 | fmt.Fprintln(w, p)
196 | }
197 | ```
198 |
199 | There's also a version of this function that allows us to retrieve multiple
200 | values at a time, therefore improving performance when we can batch operations.
201 |
202 | The function is `datastore.GetMulti`:
203 |
204 | ```go
205 | func GetMulti(c appengine.Context, key []*Key, dst interface{}) error
206 | ```
207 |
208 | ## Retreiving without keys
209 |
210 | Very often we want to find all the values in the datastore that match some
211 | conditions, such as all the values of a _kind_, or all the values where a given
212 | field equals some specific value. This is similar to SQL `SELECT` statements.
213 |
214 | They way to do this with the Datastore is using the type `datastore.Query`.
215 | Values of `datastore.Query` can be created with `datastore.NewQuery`:
216 |
217 | ```go
218 | func NewQuery(kind string) *Query
219 | ```
220 |
221 | As you can see all queries are attached to a given _kind_. Once we have a query
222 | we can add filters using the builder pattern and some of these methods:
223 |
224 | ```go
225 | func (q *Query) Ancestor(ancestor *Key) *Query
226 | ```
227 | ```go
228 | func (q *Query) Filter(filterStr string, value interface{}) *Query
229 | ```
230 | ```go
231 | func (q *Query) KeysOnly() *Query
232 | ```
233 | ```go
234 | func (q *Query) Limit(limit int) *Query
235 | ```
236 | ```go
237 | func (q *Query) Order(fieldName string) *Query
238 | ```
239 |
240 | And finally we can get the values that match our query by executing the query:
241 |
242 | ```go
243 | func (q *Query) Count(c appengine.Context) (int, error)
244 | ```
245 | ```go
246 | func (q *Query) GetAll(c appengine.Context, dst interface{}) ([]*Key, error)
247 | ```
248 | ```go
249 | func (q *Query) Run(c appengine.Context) *Iterator
250 | ```
251 |
252 | - Count returns how many values matched the query
253 | - GetAll retrieves all the values that matched the query into `dst`
254 | - Run returns a ```*datastore.Iterator``` that we can use to iterate over
255 | all the results matching the query.
256 |
257 | Let's see an example where we retrieve all the values of _kind_ `Person` that
258 | are 10 years old or younger ordered by their name.
259 |
260 | [embedmd]:# (examples/app.go /queryHandler/ /^}/)
261 | ```go
262 | queryHandler(w http.ResponseWriter, r *http.Request) {
263 | ctx := appengine.NewContext(r)
264 |
265 | var p []Person
266 |
267 | // create a new query on the kind Person
268 | q := datastore.NewQuery("Person")
269 |
270 | // select only values where field Age is 10 or lower
271 | q = q.Filter("Age <=", 10)
272 |
273 | // order all the values by the Name field
274 | q = q.Order("Name")
275 |
276 | // and finally execute the query retrieving all values into p.
277 | _, err := q.GetAll(ctx, &p)
278 | if err != nil {
279 | http.Error(w, err.Error(), http.StatusInternalServerError)
280 | return
281 | }
282 | fmt.Fprintln(w, p)
283 | }
284 | ```
285 |
286 | Note that `Filter` and `Order` return a `*datastore.Query`. This means you can
287 | chain those operations into a single expression.
288 |
289 | [embedmd]:# (examples/app.go /q :=.*\.$/ /^$/)
290 | ```go
291 | q := datastore.NewQuery("Person").
292 | Filter("Age <=", 10).
293 | Order("Name")
294 | ```
295 |
296 |
297 | You can find more information about Datastore Query
298 | [here](https://cloud.google.com/appengine/docs/go/datastore/queries).
299 |
300 | # Exercise: add durable storage to the events app
301 |
302 | Now that you know everything that there is to know (for today) for the Datastore,
303 | let's use it to make the data stored in the events application durable.
304 |
305 | Go work on the [step two](../events/step2/README.md) and come back here when you're done.
306 |
307 | # Congratulations!
308 |
309 | You are now able to store and retrieve data from the Google Cloud Datastore.
310 | With this you're now ready to build pretty much any application you can imagine!
311 |
312 | Or do you want more? In that case go the
313 | [next chapter](../section08/README.md) to learn how to access remote
314 | resources using `urlfetch`.
315 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------