├── .dockerignore
├── .gitignore
├── README.md
├── docker-compose-v1.yml
├── docker-compose-v2.yml
├── docker-compose-v3.yml
├── init_swarm_google.sh
├── init_swarm_vbox.sh
├── likes
├── Dockerfile
├── Godeps
│ ├── Godeps.json
│ └── Readme
├── README.md
├── app
│ ├── IndieFlower.ttf
│ ├── angular.min.js
│ ├── app.js
│ ├── index.html
│ └── style.css
├── likes.go
└── vendor
│ └── github.com
│ └── gorilla
│ ├── context
│ ├── .travis.yml
│ ├── LICENSE
│ ├── README.md
│ ├── context.go
│ └── doc.go
│ └── mux
│ ├── .travis.yml
│ ├── LICENSE
│ ├── README.md
│ ├── doc.go
│ ├── mux.go
│ ├── regexp.go
│ └── route.go
├── push.sh
├── web
├── Dockerfile
├── default.conf
├── nginx.conf
└── static
│ ├── IndieFlower.ttf
│ ├── angular.min.js
│ ├── app.js
│ ├── index.html
│ ├── share.png
│ └── style.css
├── words-dispatcher
├── Dockerfile
└── dispatcher.go
└── words-java
├── Dockerfile
├── pom.xml
└── src
└── main
└── java
├── AddHostName.java
├── Main.java
├── WordResponse.java
└── Words.java
/.dockerignore:
--------------------------------------------------------------------------------
1 | target
2 | *.iml
3 | Dockerfile
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.iml
3 | target/
4 | docker-compose.yml
5 | images.tar
6 | certificates
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cadavres Exquis "Swarm edition"
2 |
3 | ## Warm-up
4 |
5 | If you plan on attending this workshop, you need:
6 |
7 | * A laptop + power cord
8 | * Install Docker 1.12:
9 | * With [Docker for Mac](https://download.docker.com/mac/beta/Docker.dmg) or [Docker for Windows](https://download.docker.com/win/beta/InstallDocker.msi)
10 | * On Windows older than Windows 10 Pro, install [Docker Toolbox](https://github.com/docker/toolbox/releases/download/v1.12.0/DockerToolbox-1.12.0.exe) instead
11 | * On Linux, grab [docker-compose v1.8.1](https://github.com/docker/compose/releases/download/1.8.1/docker-compose-Linux-x86_64) and put it in your path somewhere.
12 | * Test that your docker installation works fine:
13 | * `docker version` should show version `1.12` on both Client and Server side.
14 | * Run `docker run hello-world` and check you see the welcome message.
15 | * Get the source of the lab by `git clone git@github.com:CodeStory/lab-docker.git`
16 | * During the workshop, we'll distribute USB keys with the docker images we need for the lab:
17 | * `cd lab-docker`
18 | * `docker load -i images.tar` will load all the images you need.
19 | * As an alternate solution, you can load the images directly from the hub:
20 | * `docker pull dockerdemos/lab-web`
21 | * `docker pull dockerdemos/lab-words-dispatcher`
22 | * `docker pull dockerdemos/lab-words-java`
23 | * `docker pull mongo-express:0.31.0`
24 | * `docker pull mongo:3.3.15`
25 |
26 | ## 1 - Look Ma', micro-services on my laptop
27 |
28 | Our first version of the application is composed of four micro-services:
29 |
30 | - A `web` service that uses `nginx` running on port `80` to serve an HTML5/Js
31 | application written in angularJs.
32 | - A `words-java` service that runs a `java` web server on a random port. This
33 | server connects to the database and exposes a Rest Api to the `web`.
34 | - A `db` service that runs a `mongoDb` database on a random port.
35 | - A `db-ui` service that runs a web UI on port `8081` to edit the content of
36 | the database
37 |
38 | ## Let's run the application
39 |
40 | 1. Point Docker CLI to the Docker daemon:
41 |
42 | - If you have `Docker for Mac` or `Docker for Windows`, there's nothing to be
43 | done. Run `docker info` to check that everything is up and running.
44 |
45 | - If you have `Docker Toolbox`, either open the `Quick Start` terminal or run
46 | `docker-machine env` to show the command you have to **run** to point to the
47 | Docker daemon running on the VirtualBox VM. On OSX, it's typically:
48 |
49 | ```
50 | eval $(docker-machine env default)
51 | ```
52 |
53 | 2. Configure *Docker Compose* to use the first configuration file:
54 |
55 | ```
56 | cd lab-docker
57 | cp docker-compose-v1.yml docker-compose.yml
58 | ```
59 |
60 | 3. Build and start the application:
61 |
62 | ```
63 | docker-compose up -d
64 | ```
65 |
66 | 4. Take a look at the logs, to see if there's any error:
67 |
68 | ```
69 | docker-compose logs
70 | ```
71 |
72 | 5. List the running containers:
73 |
74 | ```
75 | docker-compose ps
76 |
77 | Name Command State Ports
78 | ---------------------------------------------------------------------------------------------
79 | labdocker_db-ui_1 tini -- node app Up 0.0.0.0:8081->8081/tcp
80 | labdocker_db_1 /entrypoint.sh mongod Up 27017/tcp
81 | labdocker_web_1 nginx -g daemon off; Up 443/tcp, 0.0.0.0:80->80/tcp
82 | labdocker_words-java_1 java -DPROD_MODE=true -Xmx ... Up 8080/tcp
83 | ```
84 |
85 | ## Let's use the application
86 |
87 | With Docker for Mac and Docker for Windows, you can open a browser on "http://localhost".
88 | With Docker Toolbox, get the ip address of the VM with `docker-machine ip default` and open a browser on "http://[THE_IP]".
89 |
90 | You should see a random composed of 5 random words: a noun, an adjective,
91 | a verb, a noun and an adjective. That's a "Cadavre Exquis"! You did it!
92 |
93 | However, you'll notice that it's always the same sentence that's displayed.
94 | We have to fix that! And will do it without touching the code...
95 |
96 | ## How does it work?
97 |
98 | The angularJs application served by the `nginx` based `web` service sends 5
99 | http `GET` queries to the `nginx` that proxies the `words` REST service.
100 |
101 | That was easy because with docker, each service can be reached on the network
102 | via it's name.
103 |
104 | On each query, the `words` service loads all the words from the `mongo` database,
105 | chooses a random one and memoizes it so that future queries are served from the
106 | memory and not from the database.
107 |
108 | ## What to explore in this step
109 |
110 | 1. The `db-ui` web UI can be used to configure the list of words in the database.
111 | * Use this command to find the url for the UI: `docker-compose port db-ui 8081`
112 | * Add some nouns, adjectives and verbs, use non-plural and male noun and adjectives, or the grammar will not be correct.
113 | * **Careful**, all words added to the database at this stage will be lost for the next stages.
114 |
115 | 2. You can improve the web UI:
116 | * Change something in `web/static/index.html`
117 | * Then `docker-compose stop web; docker-compose rm -f web; docker-compose build web; docker up -d web`, see how this updates a single micro service.
118 |
119 | 3. Things to check in this step:
120 | * Notice the db connexion string in `words-java/src/main/java/Main.java`
121 | * Notice the nginx configuration in `web/default.conf` on `location /words/` and check the corresponding code in `web/static/app.js`
122 | * Notice in the `docker-compose-v1.yml` file some services have `build` and `image` instructions while others have only `image`
123 | * Notice the `ports` vs `expose` instructions, try to find a way to call the `/verb` instruction on the `words-java`, without changing the `yml` file.
124 |
125 | # 2 - Run the application with a dispatcher
126 |
127 | We are going to change the micro-service based architecture of our application
128 | without changing its code. That's neat!
129 |
130 | Our idea is to introduce an additional micro-service between the `web` and the
131 | `java` rest api. This new component is a Go based web server that will later
132 | help dispatch word queries to multiple java REST backends.
133 |
134 | ## Let's use the application
135 |
136 | 1. Stop the application currently running:
137 |
138 | ```
139 | cd lab-docker
140 | docker-compose stop
141 | docker-compose rm -f
142 | ```
143 |
144 | 2. Configure *Docker Compose* to use the second configuration file:
145 |
146 | ```
147 | cp docker-compose-v2.yml docker-compose.yml
148 | ```
149 |
150 | 3. Build and start the application:
151 |
152 | ```
153 | docker-compose up -d
154 | docker-compose logs
155 | ```
156 |
157 | As a user, you should see no difference compared to the original application.
158 | That's the whole point!
159 |
160 | ## How is that possible?
161 |
162 | The `web`'s expectation is that a `words` host exists on the network and that
163 | it responds on port `8080`. What we did it renamed the `words` service to
164 | `words-java` and introduced a new `go` based service under the name of `words`.
165 |
166 | > Step 1, we had: web:80 -> words:8080 (java)
167 | > Now, we have: web:80 -> words:8080 (go) -> words-java:8080 (java)
168 |
169 | Thanks to Docker networking and the `expose` configuration, we can have two
170 | services running on port `8080` without a conflict. An automatic translation
171 | will be done by the network. We don't have to change our code. How cool is that?
172 |
173 | ## What to explore in this step
174 |
175 | 1. Check the logs and see the dispatcher in action
176 | * Run `docker-compose logs -f` and refresh your page at will, check the dispatcher work described in the logs.
177 |
178 | 2. Check the dispatcher code
179 | * Especially the `forward` function in the `words-dispatcher/dispatcher.go` source.
180 | * **careful** this code is not really efficient but it serves well the purpose of this workshop
181 |
182 | # 3 - Run the application on a shared Swarm with Docker 1.12 services
183 |
184 | We are going to the Cloud! Your containers will be send to a shared Swarm
185 | composed of multiple nodes. We have already setup the Swarm for you before the talk.
186 | You just need to point your Docker CLI to the Swarm rather than to your local
187 | Docker daemon.
188 | This is done through environment variables. And because our Swarm has TLS enabled,
189 | you need a copy of our certificates. We'll pass along a couple of USB keys with
190 | the certificates on them. Then follow the instructions below:
191 |
192 | 1. Stop the application currently running:
193 |
194 | ```
195 | cd lab-docker
196 | docker-compose stop
197 | docker-compose rm -f
198 | ```
199 |
200 | 2. Copy the provided `certificates` from the USB key.
201 |
202 | 3. Point your docker client to the proper machine:
203 |
204 | If you are on the Google Cloud Swarm cluster:
205 |
206 | ```
207 | export DOCKER_TLS_VERIFY="1"
208 | export DOCKER_HOST="tcp://104.155.53.144:2376"
209 | export DOCKER_CERT_PATH="$(pwd)/certificates"
210 | ```
211 |
212 | 4. Confirm that `docker node ls` shows multiple nodes.
213 |
214 | 5. Configure *Docker Compose* to use the third configuration file:
215 |
216 | ```
217 | cd lab-docker
218 | cp docker-compose-v3.yml docker-compose.yml
219 | ```
220 |
221 | 5. Create a bundle file:
222 |
223 | ```
224 | docker-compose bundle -o MY-UNIQUE-TEAM-NAME.dab
225 | docker deploy MY-UNIQUE-TEAM-NAME
226 | docker service ls
227 | ```
228 |
229 | 6. Get the port of the `web` service.
230 |
231 | ```
232 | docker service inspect --pretty MY-UNIQUE-TEAM-NAME_web | tail -n1
233 | ```
234 |
235 | 7. Open the browser on `http://104.155.53.144:PORT`
236 |
237 | The same application that ran on you machine now runs in the Cloud on a shared Swarm.
238 |
239 | ## How is that possible?
240 |
241 | If you compare [docker-compose-v2.yml](docker-compose-v2.yml) and [docker-compose-v3.yml](docker-compose-v3.yml)
242 | you'll see that all the services now use a private network now. This network is
243 | created by *Docker Compose*. Its name is `private`, prefixed by the name of your
244 | project (ie your team name). It's a network available to your containers only.
245 |
246 | Thanks to this private network, multiple similar applications can coexist on a
247 | Swarm.
248 |
249 | All the services with the same name or alias on a shared network
250 | will be reachable on the same DNS name. A client can get all the IPs for
251 | the DNS name and start load balancing between the nodes. Nothing complicated
252 | to setup!
253 |
254 | That's exactly what the `words-dispatcher` does. To bypass the DNS cache, it
255 | searches for all the IPs for the `works-java` services and uses a random one
256 | each time. This effectively load balances queries among all the teams.
257 |
258 | ## What to explore in this step
259 |
260 | 1. You can increase the numbers of `words-java` nodes and see how the dispatcher react.
261 | * Add more `words-java` node with `docker service scale MY-UNIQUE-TEAM-NAME_words-java=4`.
262 | * You have now 4 `words-java` containers. Check their numbers with `docker service ls`
263 |
264 | 2. You can kill containers and see them respawned
265 |
266 | # Docker Features demonstrated
267 |
268 | * Multi-host Networking - *Docker 1.9*
269 | * New compose file - *Docker 1.10*
270 | * Use links in networks - *Docker 1.10*
271 | * Network-wide container aliases - *Docker 1.10*
272 | * DNS discovery - *Docker 1.11*
273 | * Build in docker-compose up - *Docker-Compose 1.7*
274 | * Bundles and Services - *Docker 1.12*
275 |
276 | # About 'Cadavres Exquis'
277 |
278 | Cadavres Exquis is a French word game, you'll find more on
279 | [wikipedia page](https://fr.wikipedia.org/wiki/Cadavre_exquis_(jeu)) (in French)
280 |
281 | # How did we create the Swarm?
282 |
283 | The Swarm has been created on Google Cloud with the [init_swarm_google.sh](init_swarm_google.sh) script. Take a look to what we do there but you'll need an account and this may cost you money.
284 | You can also try it on your own laptop by running the [init_swarm_vbox.sh](init_swarm_vbox.sh), you'll need virtualbox and `docker-machine`
285 |
--------------------------------------------------------------------------------
/docker-compose-v1.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | web:
5 | build: web
6 | image: dockerdemos/lab-web
7 | ports: ["80:80"]
8 |
9 | words:
10 | build: words-java
11 | image: dockerdemos/lab-words-java
12 | expose: ["8080"]
13 |
14 | db-ui:
15 | image: mongo-express:0.31.0
16 | ports: ["8081:8081"]
17 |
18 | mongo:
19 | image: mongo:3.3.15
20 | expose: ["27017"]
21 |
--------------------------------------------------------------------------------
/docker-compose-v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | web:
5 | build: web
6 | image: dockerdemos/lab-web
7 | ports: ["80:80"]
8 |
9 | words:
10 | build: words-dispatcher
11 | image: dockerdemos/lab-words-dispatcher
12 | expose: ["8080"]
13 |
14 | words-java:
15 | build: words-java
16 | image: dockerdemos/lab-words-java
17 | expose: ["8080"]
18 |
19 | db-ui:
20 | image: mongo-express:0.31.0
21 | ports: ["8081:8081"]
22 |
23 | mongo:
24 | image: mongo:3.3.15
25 | expose: ["27017"]
26 |
--------------------------------------------------------------------------------
/docker-compose-v3.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | web:
5 | image: dockerdemos/lab-web
6 | ports: ["80"]
7 | networks: ["private"]
8 |
9 | words:
10 | image: dockerdemos/lab-words-dispatcher
11 | networks: ["private"]
12 |
13 | words-java:
14 | image: dockerdemos/lab-words-java
15 | networks: ["private"]
16 |
17 | mongo:
18 | image: mongo:3.3.15
19 | networks: ["private"]
20 |
21 | networks:
22 | private:
23 |
--------------------------------------------------------------------------------
/init_swarm_google.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | export GOOGLE_PROJECT="code-story-blog"
6 | export GOOGLE_ZONE="europe-west1-d"
7 | export GOOGLE_DISK_SIZE="1000"
8 | export GOOGLE_MACHINE_TYPE="custom-2-8192"
9 |
10 | docker-machine create -d google gce-manager
11 | docker-machine create -d google gce-worker1
12 | docker-machine create -d google gce-worker2
13 | docker-machine create -d google gce-worker3
14 |
15 | docker $(docker-machine config gce-manager) \
16 | swarm init \
17 | --listen-addr $(gcloud compute instances list gce-manager --format='value(networkInterfaces.networkIP)'):2377 \
18 | --advertise-addr $(gcloud compute instances list gce-manager --format='value(networkInterfaces.networkIP)'):2377
19 |
20 | docker $(docker-machine config gce-worker1) \
21 | swarm join $(docker-machine ip gce-manager):2377 \
22 | --listen-addr $(gcloud compute instances list gce-worker1 --format='value(networkInterfaces.networkIP)'):2377 \
23 | --token $(docker $(docker-machine config gce-manager) swarm join-token worker -q)
24 |
25 | docker $(docker-machine config gce-worker2) \
26 | swarm join $(docker-machine ip gce-manager):2377 \
27 | --listen-addr $(gcloud compute instances list gce-worker2 --format='value(networkInterfaces.networkIP)'):2377 \
28 | --token $(docker $(docker-machine config gce-manager) swarm join-token worker -q)
29 |
30 | docker $(docker-machine config gce-worker3) \
31 | swarm join $(docker-machine ip gce-manager):2377 \
32 | --listen-addr $(gcloud compute instances list gce-worker3 --format='value(networkInterfaces.networkIP)'):2377 \
33 | --token $(docker $(docker-machine config gce-manager) swarm join-token worker -q)
34 |
35 | docker $(docker-machine config gce-manager) info
36 |
--------------------------------------------------------------------------------
/init_swarm_vbox.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | docker-machine create -d virtualbox manager
6 | docker-machine create -d virtualbox worker1
7 | docker-machine create -d virtualbox worker2
8 | docker-machine create -d virtualbox worker3
9 |
10 | docker $(docker-machine config manager) \
11 | swarm init \
12 | --listen-addr $(docker-machine ip manager):2377 \
13 | --advertise-addr $(docker-machine ip manager):2377
14 |
15 | docker $(docker-machine config worker1) \
16 | swarm join $(docker-machine ip manager):2377 \
17 | --listen-addr $(docker-machine ip worker1):2377 \
18 | --token $(docker $(docker-machine config manager) swarm join-token worker -q)
19 |
20 | docker $(docker-machine config worker2) \
21 | swarm join $(docker-machine ip manager):2377 \
22 | --listen-addr $(docker-machine ip worker2):2377 \
23 | --token $(docker $(docker-machine config manager) swarm join-token worker -q)
24 |
25 | docker $(docker-machine config worker3) \
26 | swarm join $(docker-machine ip manager):2377 \
27 | --listen-addr $(docker-machine ip worker3):2377 \
28 | --token $(docker $(docker-machine config manager) swarm join-token worker -q)
29 |
30 | docker $(docker-machine config manager) info
31 |
--------------------------------------------------------------------------------
/likes/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.6.1-alpine
2 |
3 | EXPOSE 8080
4 | WORKDIR /go/src/likes
5 | CMD ["./likes"]
6 |
7 | COPY . ./
8 | RUN go get -d -v && go build -v
9 |
--------------------------------------------------------------------------------
/likes/Godeps/Godeps.json:
--------------------------------------------------------------------------------
1 | {
2 | "ImportPath": "github.com/dgageot/lab-docker/likes",
3 | "GoVersion": "go1.6",
4 | "GodepVersion": "v62",
5 | "Deps": [
6 | {
7 | "ImportPath": "github.com/gorilla/context",
8 | "Rev": "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
9 | },
10 | {
11 | "ImportPath": "github.com/gorilla/mux",
12 | "Comment": "v1.1",
13 | "Rev": "0eeaf8392f5b04950925b8a69fe70f110fa7cbfc"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/likes/Godeps/Readme:
--------------------------------------------------------------------------------
1 | This directory tree is generated automatically by godep.
2 |
3 | Please do not edit.
4 |
5 | See https://github.com/tools/godep for more information.
6 |
--------------------------------------------------------------------------------
/likes/README.md:
--------------------------------------------------------------------------------
1 | ```
2 | $ http GET localhost:8080/likes
3 | HTTP/1.1 200 OK
4 | Content-Length: 31
5 | Content-Type: application/json
6 | Date: Sat, 09 Apr 2016 14:10:07 GMT
7 |
8 | [
9 | {
10 | "Name": "foo"
11 | },
12 | {
13 | "Name": "bar"
14 | }
15 | ]
16 |
17 | $ http PUT localhost:8080/likes name=polka
18 | HTTP/1.1 201 Created
19 | Content-Length: 0
20 | Content-Type: application/json
21 | Date: Sat, 09 Apr 2016 14:10:14 GMT
22 |
23 |
24 |
25 | $ http GET localhost:8080/likes
26 | HTTP/1.1 200 OK
27 | Content-Length: 48
28 | Content-Type: application/json
29 | Date: Sat, 09 Apr 2016 14:10:17 GMT
30 |
31 | [
32 | {
33 | "Name": "foo"
34 | },
35 | {
36 | "Name": "bar"
37 | },
38 | {
39 | "Name": "polka"
40 | }
41 | ]
42 | ```
43 |
--------------------------------------------------------------------------------
/likes/app/IndieFlower.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStory/lab-docker/7a8e12822aadc28aa91e3c4c59e6cb044f2229d9/likes/app/IndieFlower.ttf
--------------------------------------------------------------------------------
/likes/app/app.js:
--------------------------------------------------------------------------------
1 | var likeApp = angular.module('likeApp', []);
2 |
3 | likeApp.controller('likeController', function ($scope, $http) {
4 | $http.get('/likes').success(function(data){
5 | $scope.likes = data;
6 | });
7 | });
--------------------------------------------------------------------------------
/likes/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Like
6 |
7 |
8 |
9 |
Cadavre Exquis
10 |
11 |
{{like.name}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/likes/app/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Indie Flower';
3 | font-style: normal;
4 | font-weight: 400;
5 | src: local('Indie Flower'), local('IndieFlower'), url('IndieFlower.ttf') format('woff2');
6 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
7 | }
8 |
9 | body {
10 | font-family: 'Indie Flower', cursive;
11 | }
12 |
13 | h1 {
14 | margin:0;
15 | text-align: center;
16 | font-size: 80px;
17 | }
18 |
19 | ul
20 | {
21 | list-style-type: none;
22 | }
23 |
24 | li {
25 | text-decoration: none;
26 | font-size: 60px;
27 | }
28 |
--------------------------------------------------------------------------------
/likes/likes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "io/ioutil"
7 | "github.com/gorilla/mux"
8 | "encoding/json"
9 | )
10 |
11 | // Like a rolling stone
12 | type Like struct {
13 | Name string `json:"name"`
14 | }
15 |
16 | // Server serves
17 | type Server struct {
18 | likes []Like
19 | }
20 |
21 | func (server *Server) start(port int) {
22 | router := mux.NewRouter()
23 | router.HandleFunc("/likes", server.options).Methods("OPTIONS")
24 | router.HandleFunc("/likes", server.allLike).Methods("GET")
25 | router.HandleFunc("/likes", server.addLike).Methods("POST")
26 | router.PathPrefix("/").Handler(http.FileServer(http.Dir("./app")))
27 | http.Handle("/", router)
28 |
29 | fmt.Println("Listening on", port)
30 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
31 | }
32 |
33 | func main() {
34 | server := Server{[]Like{Like{"foo"},Like{"bar"}}}
35 | server.start(8080)
36 | }
37 |
38 | func (server *Server) addLike(writer http.ResponseWriter, request *http.Request ) {
39 | bytes, err := ioutil.ReadAll(request.Body)
40 | if err != nil {
41 | http.Error(writer, err.Error(), http.StatusInternalServerError)
42 | return
43 | }
44 | like := Like{}
45 | err = json.Unmarshal(bytes, &like)
46 | if err != nil {
47 | http.Error(writer, err.Error(), http.StatusInternalServerError)
48 | return
49 | }
50 | server.likes = append([]Like{like}, server.likes...)
51 | writeCORSHeader(writer)
52 | writer.Header().Set("Content-Type", "application/json")
53 | writer.WriteHeader(http.StatusCreated)
54 | }
55 |
56 | func (server *Server) allLike(writer http.ResponseWriter, request *http.Request ) {
57 | payload, err := json.Marshal(server.likes)
58 | if err != nil {
59 | http.Error(writer, err.Error(), http.StatusInternalServerError)
60 | return
61 | }
62 | writer.Header().Set("Content-Type", "application/json")
63 | writer.Write(payload)
64 | }
65 |
66 | func (server *Server) options(writer http.ResponseWriter, request *http.Request ) {
67 | writeCORSHeader(writer)
68 | writer.WriteHeader(http.StatusOK)
69 | }
70 |
71 | func writeCORSHeader(writer http.ResponseWriter) {
72 | writer.Header().Set("Access-Control-Allow-Origin","*")
73 | writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
74 | writer.Header().Set("Access-Control-Allow-Headers", "X-DOCKERFTW, Content-Type")
75 | }
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/context/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | sudo: false
3 |
4 | matrix:
5 | include:
6 | - go: 1.3
7 | - go: 1.4
8 | - go: 1.5
9 | - go: 1.6
10 | - go: tip
11 |
12 | install:
13 | - go get golang.org/x/tools/cmd/vet
14 |
15 | script:
16 | - go get -t -v ./...
17 | - diff -u <(echo -n) <(gofmt -d .)
18 | - go tool vet .
19 | - go test -v -race ./...
20 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/context/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/context/README.md:
--------------------------------------------------------------------------------
1 | context
2 | =======
3 | [](https://travis-ci.org/gorilla/context)
4 |
5 | gorilla/context is a general purpose registry for global request variables.
6 |
7 | Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
8 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/context/context.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package context
6 |
7 | import (
8 | "net/http"
9 | "sync"
10 | "time"
11 | )
12 |
13 | var (
14 | mutex sync.RWMutex
15 | data = make(map[*http.Request]map[interface{}]interface{})
16 | datat = make(map[*http.Request]int64)
17 | )
18 |
19 | // Set stores a value for a given key in a given request.
20 | func Set(r *http.Request, key, val interface{}) {
21 | mutex.Lock()
22 | if data[r] == nil {
23 | data[r] = make(map[interface{}]interface{})
24 | datat[r] = time.Now().Unix()
25 | }
26 | data[r][key] = val
27 | mutex.Unlock()
28 | }
29 |
30 | // Get returns a value stored for a given key in a given request.
31 | func Get(r *http.Request, key interface{}) interface{} {
32 | mutex.RLock()
33 | if ctx := data[r]; ctx != nil {
34 | value := ctx[key]
35 | mutex.RUnlock()
36 | return value
37 | }
38 | mutex.RUnlock()
39 | return nil
40 | }
41 |
42 | // GetOk returns stored value and presence state like multi-value return of map access.
43 | func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
44 | mutex.RLock()
45 | if _, ok := data[r]; ok {
46 | value, ok := data[r][key]
47 | mutex.RUnlock()
48 | return value, ok
49 | }
50 | mutex.RUnlock()
51 | return nil, false
52 | }
53 |
54 | // GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
55 | func GetAll(r *http.Request) map[interface{}]interface{} {
56 | mutex.RLock()
57 | if context, ok := data[r]; ok {
58 | result := make(map[interface{}]interface{}, len(context))
59 | for k, v := range context {
60 | result[k] = v
61 | }
62 | mutex.RUnlock()
63 | return result
64 | }
65 | mutex.RUnlock()
66 | return nil
67 | }
68 |
69 | // GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
70 | // the request was registered.
71 | func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
72 | mutex.RLock()
73 | context, ok := data[r]
74 | result := make(map[interface{}]interface{}, len(context))
75 | for k, v := range context {
76 | result[k] = v
77 | }
78 | mutex.RUnlock()
79 | return result, ok
80 | }
81 |
82 | // Delete removes a value stored for a given key in a given request.
83 | func Delete(r *http.Request, key interface{}) {
84 | mutex.Lock()
85 | if data[r] != nil {
86 | delete(data[r], key)
87 | }
88 | mutex.Unlock()
89 | }
90 |
91 | // Clear removes all values stored for a given request.
92 | //
93 | // This is usually called by a handler wrapper to clean up request
94 | // variables at the end of a request lifetime. See ClearHandler().
95 | func Clear(r *http.Request) {
96 | mutex.Lock()
97 | clear(r)
98 | mutex.Unlock()
99 | }
100 |
101 | // clear is Clear without the lock.
102 | func clear(r *http.Request) {
103 | delete(data, r)
104 | delete(datat, r)
105 | }
106 |
107 | // Purge removes request data stored for longer than maxAge, in seconds.
108 | // It returns the amount of requests removed.
109 | //
110 | // If maxAge <= 0, all request data is removed.
111 | //
112 | // This is only used for sanity check: in case context cleaning was not
113 | // properly set some request data can be kept forever, consuming an increasing
114 | // amount of memory. In case this is detected, Purge() must be called
115 | // periodically until the problem is fixed.
116 | func Purge(maxAge int) int {
117 | mutex.Lock()
118 | count := 0
119 | if maxAge <= 0 {
120 | count = len(data)
121 | data = make(map[*http.Request]map[interface{}]interface{})
122 | datat = make(map[*http.Request]int64)
123 | } else {
124 | min := time.Now().Unix() - int64(maxAge)
125 | for r := range data {
126 | if datat[r] < min {
127 | clear(r)
128 | count++
129 | }
130 | }
131 | }
132 | mutex.Unlock()
133 | return count
134 | }
135 |
136 | // ClearHandler wraps an http.Handler and clears request values at the end
137 | // of a request lifetime.
138 | func ClearHandler(h http.Handler) http.Handler {
139 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
140 | defer Clear(r)
141 | h.ServeHTTP(w, r)
142 | })
143 | }
144 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/context/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package context stores values shared during a request lifetime.
7 |
8 | For example, a router can set variables extracted from the URL and later
9 | application handlers can access those values, or it can be used to store
10 | sessions values to be saved at the end of a request. There are several
11 | others common uses.
12 |
13 | The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
14 |
15 | http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
16 |
17 | Here's the basic usage: first define the keys that you will need. The key
18 | type is interface{} so a key can be of any type that supports equality.
19 | Here we define a key using a custom int type to avoid name collisions:
20 |
21 | package foo
22 |
23 | import (
24 | "github.com/gorilla/context"
25 | )
26 |
27 | type key int
28 |
29 | const MyKey key = 0
30 |
31 | Then set a variable. Variables are bound to an http.Request object, so you
32 | need a request instance to set a value:
33 |
34 | context.Set(r, MyKey, "bar")
35 |
36 | The application can later access the variable using the same key you provided:
37 |
38 | func MyHandler(w http.ResponseWriter, r *http.Request) {
39 | // val is "bar".
40 | val := context.Get(r, foo.MyKey)
41 |
42 | // returns ("bar", true)
43 | val, ok := context.GetOk(r, foo.MyKey)
44 | // ...
45 | }
46 |
47 | And that's all about the basic usage. We discuss some other ideas below.
48 |
49 | Any type can be stored in the context. To enforce a given type, make the key
50 | private and wrap Get() and Set() to accept and return values of a specific
51 | type:
52 |
53 | type key int
54 |
55 | const mykey key = 0
56 |
57 | // GetMyKey returns a value for this package from the request values.
58 | func GetMyKey(r *http.Request) SomeType {
59 | if rv := context.Get(r, mykey); rv != nil {
60 | return rv.(SomeType)
61 | }
62 | return nil
63 | }
64 |
65 | // SetMyKey sets a value for this package in the request values.
66 | func SetMyKey(r *http.Request, val SomeType) {
67 | context.Set(r, mykey, val)
68 | }
69 |
70 | Variables must be cleared at the end of a request, to remove all values
71 | that were stored. This can be done in an http.Handler, after a request was
72 | served. Just call Clear() passing the request:
73 |
74 | context.Clear(r)
75 |
76 | ...or use ClearHandler(), which conveniently wraps an http.Handler to clear
77 | variables at the end of a request lifetime.
78 |
79 | The Routers from the packages gorilla/mux and gorilla/pat call Clear()
80 | so if you are using either of them you don't need to clear the context manually.
81 | */
82 | package context
83 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | sudo: false
3 |
4 | matrix:
5 | include:
6 | - go: 1.2
7 | - go: 1.3
8 | - go: 1.4
9 | - go: 1.5
10 | - go: 1.6
11 | - go: tip
12 |
13 | install:
14 | - go get golang.org/x/tools/cmd/vet
15 |
16 | script:
17 | - go get -t -v ./...
18 | - diff -u <(echo -n) <(gofmt -d .)
19 | - go tool vet .
20 | - go test -v -race ./...
21 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/README.md:
--------------------------------------------------------------------------------
1 | mux
2 | ===
3 | [](https://godoc.org/github.com/gorilla/mux)
4 | [](https://travis-ci.org/gorilla/mux)
5 |
6 | http://www.gorillatoolkit.org/pkg/mux
7 |
8 | Package `gorilla/mux` implements a request router and dispatcher.
9 |
10 | The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
11 |
12 | * Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
13 | * URL hosts and paths can have variables with an optional regular expression.
14 | * Registered URLs can be built, or "reversed", which helps maintaining references to resources.
15 | * Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
16 | * It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
17 |
18 | Let's start registering a couple of URL paths and handlers:
19 |
20 | ```go
21 | func main() {
22 | r := mux.NewRouter()
23 | r.HandleFunc("/", HomeHandler)
24 | r.HandleFunc("/products", ProductsHandler)
25 | r.HandleFunc("/articles", ArticlesHandler)
26 | http.Handle("/", r)
27 | }
28 | ```
29 |
30 | Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
31 |
32 | Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
33 |
34 | ```go
35 | r := mux.NewRouter()
36 | r.HandleFunc("/products/{key}", ProductHandler)
37 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
38 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
39 | ```
40 |
41 | The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
42 |
43 | ```go
44 | vars := mux.Vars(request)
45 | category := vars["category"]
46 | ```
47 |
48 | And this is all you need to know about the basic usage. More advanced options are explained below.
49 |
50 | Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
51 |
52 | ```go
53 | r := mux.NewRouter()
54 | // Only matches if domain is "www.example.com".
55 | r.Host("www.example.com")
56 | // Matches a dynamic subdomain.
57 | r.Host("{subdomain:[a-z]+}.domain.com")
58 | ```
59 |
60 | There are several other matchers that can be added. To match path prefixes:
61 |
62 | ```go
63 | r.PathPrefix("/products/")
64 | ```
65 |
66 | ...or HTTP methods:
67 |
68 | ```go
69 | r.Methods("GET", "POST")
70 | ```
71 |
72 | ...or URL schemes:
73 |
74 | ```go
75 | r.Schemes("https")
76 | ```
77 |
78 | ...or header values:
79 |
80 | ```go
81 | r.Headers("X-Requested-With", "XMLHttpRequest")
82 | ```
83 |
84 | ...or query values:
85 |
86 | ```go
87 | r.Queries("key", "value")
88 | ```
89 |
90 | ...or to use a custom matcher function:
91 |
92 | ```go
93 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
94 | return r.ProtoMajor == 0
95 | })
96 | ```
97 |
98 | ...and finally, it is possible to combine several matchers in a single route:
99 |
100 | ```go
101 | r.HandleFunc("/products", ProductsHandler).
102 | Host("www.example.com").
103 | Methods("GET").
104 | Schemes("http")
105 | ```
106 |
107 | Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
108 |
109 | For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
110 |
111 | ```go
112 | r := mux.NewRouter()
113 | s := r.Host("www.example.com").Subrouter()
114 | ```
115 |
116 | Then register routes in the subrouter:
117 |
118 | ```go
119 | s.HandleFunc("/products/", ProductsHandler)
120 | s.HandleFunc("/products/{key}", ProductHandler)
121 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
122 | ```
123 |
124 | The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
125 |
126 | Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
127 |
128 | There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
129 |
130 | ```go
131 | r := mux.NewRouter()
132 | s := r.PathPrefix("/products").Subrouter()
133 | // "/products/"
134 | s.HandleFunc("/", ProductsHandler)
135 | // "/products/{key}/"
136 | s.HandleFunc("/{key}/", ProductHandler)
137 | // "/products/{key}/details"
138 | s.HandleFunc("/{key}/details", ProductDetailsHandler)
139 | ```
140 |
141 | Now let's see how to build registered URLs.
142 |
143 | Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
144 |
145 | ```go
146 | r := mux.NewRouter()
147 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
148 | Name("article")
149 | ```
150 |
151 | To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
152 |
153 | ```go
154 | url, err := r.Get("article").URL("category", "technology", "id", "42")
155 | ```
156 |
157 | ...and the result will be a `url.URL` with the following path:
158 |
159 | ```
160 | "/articles/technology/42"
161 | ```
162 |
163 | This also works for host variables:
164 |
165 | ```go
166 | r := mux.NewRouter()
167 | r.Host("{subdomain}.domain.com").
168 | Path("/articles/{category}/{id:[0-9]+}").
169 | HandlerFunc(ArticleHandler).
170 | Name("article")
171 |
172 | // url.String() will be "http://news.domain.com/articles/technology/42"
173 | url, err := r.Get("article").URL("subdomain", "news",
174 | "category", "technology",
175 | "id", "42")
176 | ```
177 |
178 | All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
179 |
180 | Regex support also exists for matching Headers within a route. For example, we could do:
181 |
182 | ```go
183 | r.HeadersRegexp("Content-Type", "application/(text|json)")
184 | ```
185 |
186 | ...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
187 |
188 | There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
189 |
190 | ```go
191 | // "http://news.domain.com/"
192 | host, err := r.Get("article").URLHost("subdomain", "news")
193 |
194 | // "/articles/technology/42"
195 | path, err := r.Get("article").URLPath("category", "technology", "id", "42")
196 | ```
197 |
198 | And if you use subrouters, host and path defined separately can be built as well:
199 |
200 | ```go
201 | r := mux.NewRouter()
202 | s := r.Host("{subdomain}.domain.com").Subrouter()
203 | s.Path("/articles/{category}/{id:[0-9]+}").
204 | HandlerFunc(ArticleHandler).
205 | Name("article")
206 |
207 | // "http://news.domain.com/articles/technology/42"
208 | url, err := r.Get("article").URL("subdomain", "news",
209 | "category", "technology",
210 | "id", "42")
211 | ```
212 |
213 | ## Full Example
214 |
215 | Here's a complete, runnable example of a small `mux` based server:
216 |
217 | ```go
218 | package main
219 |
220 | import (
221 | "net/http"
222 |
223 | "github.com/gorilla/mux"
224 | )
225 |
226 | func YourHandler(w http.ResponseWriter, r *http.Request) {
227 | w.Write([]byte("Gorilla!\n"))
228 | }
229 |
230 | func main() {
231 | r := mux.NewRouter()
232 | // Routes consist of a path and a handler function.
233 | r.HandleFunc("/", YourHandler)
234 |
235 | // Bind to a port and pass our router in
236 | http.ListenAndServe(":8000", r)
237 | }
238 | ```
239 |
240 | ## License
241 |
242 | BSD licensed. See the LICENSE file for details.
243 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | /*
6 | Package mux implements a request router and dispatcher.
7 |
8 | The name mux stands for "HTTP request multiplexer". Like the standard
9 | http.ServeMux, mux.Router matches incoming requests against a list of
10 | registered routes and calls a handler for the route that matches the URL
11 | or other conditions. The main features are:
12 |
13 | * Requests can be matched based on URL host, path, path prefix, schemes,
14 | header and query values, HTTP methods or using custom matchers.
15 | * URL hosts and paths can have variables with an optional regular
16 | expression.
17 | * Registered URLs can be built, or "reversed", which helps maintaining
18 | references to resources.
19 | * Routes can be used as subrouters: nested routes are only tested if the
20 | parent route matches. This is useful to define groups of routes that
21 | share common conditions like a host, a path prefix or other repeated
22 | attributes. As a bonus, this optimizes request matching.
23 | * It implements the http.Handler interface so it is compatible with the
24 | standard http.ServeMux.
25 |
26 | Let's start registering a couple of URL paths and handlers:
27 |
28 | func main() {
29 | r := mux.NewRouter()
30 | r.HandleFunc("/", HomeHandler)
31 | r.HandleFunc("/products", ProductsHandler)
32 | r.HandleFunc("/articles", ArticlesHandler)
33 | http.Handle("/", r)
34 | }
35 |
36 | Here we register three routes mapping URL paths to handlers. This is
37 | equivalent to how http.HandleFunc() works: if an incoming request URL matches
38 | one of the paths, the corresponding handler is called passing
39 | (http.ResponseWriter, *http.Request) as parameters.
40 |
41 | Paths can have variables. They are defined using the format {name} or
42 | {name:pattern}. If a regular expression pattern is not defined, the matched
43 | variable will be anything until the next slash. For example:
44 |
45 | r := mux.NewRouter()
46 | r.HandleFunc("/products/{key}", ProductHandler)
47 | r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
48 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
49 |
50 | The names are used to create a map of route variables which can be retrieved
51 | calling mux.Vars():
52 |
53 | vars := mux.Vars(request)
54 | category := vars["category"]
55 |
56 | And this is all you need to know about the basic usage. More advanced options
57 | are explained below.
58 |
59 | Routes can also be restricted to a domain or subdomain. Just define a host
60 | pattern to be matched. They can also have variables:
61 |
62 | r := mux.NewRouter()
63 | // Only matches if domain is "www.example.com".
64 | r.Host("www.example.com")
65 | // Matches a dynamic subdomain.
66 | r.Host("{subdomain:[a-z]+}.domain.com")
67 |
68 | There are several other matchers that can be added. To match path prefixes:
69 |
70 | r.PathPrefix("/products/")
71 |
72 | ...or HTTP methods:
73 |
74 | r.Methods("GET", "POST")
75 |
76 | ...or URL schemes:
77 |
78 | r.Schemes("https")
79 |
80 | ...or header values:
81 |
82 | r.Headers("X-Requested-With", "XMLHttpRequest")
83 |
84 | ...or query values:
85 |
86 | r.Queries("key", "value")
87 |
88 | ...or to use a custom matcher function:
89 |
90 | r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
91 | return r.ProtoMajor == 0
92 | })
93 |
94 | ...and finally, it is possible to combine several matchers in a single route:
95 |
96 | r.HandleFunc("/products", ProductsHandler).
97 | Host("www.example.com").
98 | Methods("GET").
99 | Schemes("http")
100 |
101 | Setting the same matching conditions again and again can be boring, so we have
102 | a way to group several routes that share the same requirements.
103 | We call it "subrouting".
104 |
105 | For example, let's say we have several URLs that should only match when the
106 | host is "www.example.com". Create a route for that host and get a "subrouter"
107 | from it:
108 |
109 | r := mux.NewRouter()
110 | s := r.Host("www.example.com").Subrouter()
111 |
112 | Then register routes in the subrouter:
113 |
114 | s.HandleFunc("/products/", ProductsHandler)
115 | s.HandleFunc("/products/{key}", ProductHandler)
116 | s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
117 |
118 | The three URL paths we registered above will only be tested if the domain is
119 | "www.example.com", because the subrouter is tested first. This is not
120 | only convenient, but also optimizes request matching. You can create
121 | subrouters combining any attribute matchers accepted by a route.
122 |
123 | Subrouters can be used to create domain or path "namespaces": you define
124 | subrouters in a central place and then parts of the app can register its
125 | paths relatively to a given subrouter.
126 |
127 | There's one more thing about subroutes. When a subrouter has a path prefix,
128 | the inner routes use it as base for their paths:
129 |
130 | r := mux.NewRouter()
131 | s := r.PathPrefix("/products").Subrouter()
132 | // "/products/"
133 | s.HandleFunc("/", ProductsHandler)
134 | // "/products/{key}/"
135 | s.HandleFunc("/{key}/", ProductHandler)
136 | // "/products/{key}/details"
137 | s.HandleFunc("/{key}/details", ProductDetailsHandler)
138 |
139 | Now let's see how to build registered URLs.
140 |
141 | Routes can be named. All routes that define a name can have their URLs built,
142 | or "reversed". We define a name calling Name() on a route. For example:
143 |
144 | r := mux.NewRouter()
145 | r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
146 | Name("article")
147 |
148 | To build a URL, get the route and call the URL() method, passing a sequence of
149 | key/value pairs for the route variables. For the previous route, we would do:
150 |
151 | url, err := r.Get("article").URL("category", "technology", "id", "42")
152 |
153 | ...and the result will be a url.URL with the following path:
154 |
155 | "/articles/technology/42"
156 |
157 | This also works for host variables:
158 |
159 | r := mux.NewRouter()
160 | r.Host("{subdomain}.domain.com").
161 | Path("/articles/{category}/{id:[0-9]+}").
162 | HandlerFunc(ArticleHandler).
163 | Name("article")
164 |
165 | // url.String() will be "http://news.domain.com/articles/technology/42"
166 | url, err := r.Get("article").URL("subdomain", "news",
167 | "category", "technology",
168 | "id", "42")
169 |
170 | All variables defined in the route are required, and their values must
171 | conform to the corresponding patterns. These requirements guarantee that a
172 | generated URL will always match a registered route -- the only exception is
173 | for explicitly defined "build-only" routes which never match.
174 |
175 | Regex support also exists for matching Headers within a route. For example, we could do:
176 |
177 | r.HeadersRegexp("Content-Type", "application/(text|json)")
178 |
179 | ...and the route will match both requests with a Content-Type of `application/json` as well as
180 | `application/text`
181 |
182 | There's also a way to build only the URL host or path for a route:
183 | use the methods URLHost() or URLPath() instead. For the previous route,
184 | we would do:
185 |
186 | // "http://news.domain.com/"
187 | host, err := r.Get("article").URLHost("subdomain", "news")
188 |
189 | // "/articles/technology/42"
190 | path, err := r.Get("article").URLPath("category", "technology", "id", "42")
191 |
192 | And if you use subrouters, host and path defined separately can be built
193 | as well:
194 |
195 | r := mux.NewRouter()
196 | s := r.Host("{subdomain}.domain.com").Subrouter()
197 | s.Path("/articles/{category}/{id:[0-9]+}").
198 | HandlerFunc(ArticleHandler).
199 | Name("article")
200 |
201 | // "http://news.domain.com/articles/technology/42"
202 | url, err := r.Get("article").URL("subdomain", "news",
203 | "category", "technology",
204 | "id", "42")
205 | */
206 | package mux
207 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/mux.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "net/http"
11 | "path"
12 | "regexp"
13 |
14 | "github.com/gorilla/context"
15 | )
16 |
17 | // NewRouter returns a new router instance.
18 | func NewRouter() *Router {
19 | return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
20 | }
21 |
22 | // Router registers routes to be matched and dispatches a handler.
23 | //
24 | // It implements the http.Handler interface, so it can be registered to serve
25 | // requests:
26 | //
27 | // var router = mux.NewRouter()
28 | //
29 | // func main() {
30 | // http.Handle("/", router)
31 | // }
32 | //
33 | // Or, for Google App Engine, register it in a init() function:
34 | //
35 | // func init() {
36 | // http.Handle("/", router)
37 | // }
38 | //
39 | // This will send all incoming requests to the router.
40 | type Router struct {
41 | // Configurable Handler to be used when no route matches.
42 | NotFoundHandler http.Handler
43 | // Parent route, if this is a subrouter.
44 | parent parentRoute
45 | // Routes to be matched, in order.
46 | routes []*Route
47 | // Routes by name for URL building.
48 | namedRoutes map[string]*Route
49 | // See Router.StrictSlash(). This defines the flag for new routes.
50 | strictSlash bool
51 | // If true, do not clear the request context after handling the request
52 | KeepContext bool
53 | }
54 |
55 | // Match matches registered routes against the request.
56 | func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
57 | for _, route := range r.routes {
58 | if route.Match(req, match) {
59 | return true
60 | }
61 | }
62 |
63 | // Closest match for a router (includes sub-routers)
64 | if r.NotFoundHandler != nil {
65 | match.Handler = r.NotFoundHandler
66 | return true
67 | }
68 | return false
69 | }
70 |
71 | // ServeHTTP dispatches the handler registered in the matched route.
72 | //
73 | // When there is a match, the route variables can be retrieved calling
74 | // mux.Vars(request).
75 | func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
76 | // Clean path to canonical form and redirect.
77 | if p := cleanPath(req.URL.Path); p != req.URL.Path {
78 |
79 | // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
80 | // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
81 | // http://code.google.com/p/go/issues/detail?id=5252
82 | url := *req.URL
83 | url.Path = p
84 | p = url.String()
85 |
86 | w.Header().Set("Location", p)
87 | w.WriteHeader(http.StatusMovedPermanently)
88 | return
89 | }
90 | var match RouteMatch
91 | var handler http.Handler
92 | if r.Match(req, &match) {
93 | handler = match.Handler
94 | setVars(req, match.Vars)
95 | setCurrentRoute(req, match.Route)
96 | }
97 | if handler == nil {
98 | handler = http.NotFoundHandler()
99 | }
100 | if !r.KeepContext {
101 | defer context.Clear(req)
102 | }
103 | handler.ServeHTTP(w, req)
104 | }
105 |
106 | // Get returns a route registered with the given name.
107 | func (r *Router) Get(name string) *Route {
108 | return r.getNamedRoutes()[name]
109 | }
110 |
111 | // GetRoute returns a route registered with the given name. This method
112 | // was renamed to Get() and remains here for backwards compatibility.
113 | func (r *Router) GetRoute(name string) *Route {
114 | return r.getNamedRoutes()[name]
115 | }
116 |
117 | // StrictSlash defines the trailing slash behavior for new routes. The initial
118 | // value is false.
119 | //
120 | // When true, if the route path is "/path/", accessing "/path" will redirect
121 | // to the former and vice versa. In other words, your application will always
122 | // see the path as specified in the route.
123 | //
124 | // When false, if the route path is "/path", accessing "/path/" will not match
125 | // this route and vice versa.
126 | //
127 | // Special case: when a route sets a path prefix using the PathPrefix() method,
128 | // strict slash is ignored for that route because the redirect behavior can't
129 | // be determined from a prefix alone. However, any subrouters created from that
130 | // route inherit the original StrictSlash setting.
131 | func (r *Router) StrictSlash(value bool) *Router {
132 | r.strictSlash = value
133 | return r
134 | }
135 |
136 | // ----------------------------------------------------------------------------
137 | // parentRoute
138 | // ----------------------------------------------------------------------------
139 |
140 | // getNamedRoutes returns the map where named routes are registered.
141 | func (r *Router) getNamedRoutes() map[string]*Route {
142 | if r.namedRoutes == nil {
143 | if r.parent != nil {
144 | r.namedRoutes = r.parent.getNamedRoutes()
145 | } else {
146 | r.namedRoutes = make(map[string]*Route)
147 | }
148 | }
149 | return r.namedRoutes
150 | }
151 |
152 | // getRegexpGroup returns regexp definitions from the parent route, if any.
153 | func (r *Router) getRegexpGroup() *routeRegexpGroup {
154 | if r.parent != nil {
155 | return r.parent.getRegexpGroup()
156 | }
157 | return nil
158 | }
159 |
160 | func (r *Router) buildVars(m map[string]string) map[string]string {
161 | if r.parent != nil {
162 | m = r.parent.buildVars(m)
163 | }
164 | return m
165 | }
166 |
167 | // ----------------------------------------------------------------------------
168 | // Route factories
169 | // ----------------------------------------------------------------------------
170 |
171 | // NewRoute registers an empty route.
172 | func (r *Router) NewRoute() *Route {
173 | route := &Route{parent: r, strictSlash: r.strictSlash}
174 | r.routes = append(r.routes, route)
175 | return route
176 | }
177 |
178 | // Handle registers a new route with a matcher for the URL path.
179 | // See Route.Path() and Route.Handler().
180 | func (r *Router) Handle(path string, handler http.Handler) *Route {
181 | return r.NewRoute().Path(path).Handler(handler)
182 | }
183 |
184 | // HandleFunc registers a new route with a matcher for the URL path.
185 | // See Route.Path() and Route.HandlerFunc().
186 | func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
187 | *http.Request)) *Route {
188 | return r.NewRoute().Path(path).HandlerFunc(f)
189 | }
190 |
191 | // Headers registers a new route with a matcher for request header values.
192 | // See Route.Headers().
193 | func (r *Router) Headers(pairs ...string) *Route {
194 | return r.NewRoute().Headers(pairs...)
195 | }
196 |
197 | // Host registers a new route with a matcher for the URL host.
198 | // See Route.Host().
199 | func (r *Router) Host(tpl string) *Route {
200 | return r.NewRoute().Host(tpl)
201 | }
202 |
203 | // MatcherFunc registers a new route with a custom matcher function.
204 | // See Route.MatcherFunc().
205 | func (r *Router) MatcherFunc(f MatcherFunc) *Route {
206 | return r.NewRoute().MatcherFunc(f)
207 | }
208 |
209 | // Methods registers a new route with a matcher for HTTP methods.
210 | // See Route.Methods().
211 | func (r *Router) Methods(methods ...string) *Route {
212 | return r.NewRoute().Methods(methods...)
213 | }
214 |
215 | // Path registers a new route with a matcher for the URL path.
216 | // See Route.Path().
217 | func (r *Router) Path(tpl string) *Route {
218 | return r.NewRoute().Path(tpl)
219 | }
220 |
221 | // PathPrefix registers a new route with a matcher for the URL path prefix.
222 | // See Route.PathPrefix().
223 | func (r *Router) PathPrefix(tpl string) *Route {
224 | return r.NewRoute().PathPrefix(tpl)
225 | }
226 |
227 | // Queries registers a new route with a matcher for URL query values.
228 | // See Route.Queries().
229 | func (r *Router) Queries(pairs ...string) *Route {
230 | return r.NewRoute().Queries(pairs...)
231 | }
232 |
233 | // Schemes registers a new route with a matcher for URL schemes.
234 | // See Route.Schemes().
235 | func (r *Router) Schemes(schemes ...string) *Route {
236 | return r.NewRoute().Schemes(schemes...)
237 | }
238 |
239 | // BuildVarsFunc registers a new route with a custom function for modifying
240 | // route variables before building a URL.
241 | func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
242 | return r.NewRoute().BuildVarsFunc(f)
243 | }
244 |
245 | // Walk walks the router and all its sub-routers, calling walkFn for each route
246 | // in the tree. The routes are walked in the order they were added. Sub-routers
247 | // are explored depth-first.
248 | func (r *Router) Walk(walkFn WalkFunc) error {
249 | return r.walk(walkFn, []*Route{})
250 | }
251 |
252 | // SkipRouter is used as a return value from WalkFuncs to indicate that the
253 | // router that walk is about to descend down to should be skipped.
254 | var SkipRouter = errors.New("skip this router")
255 |
256 | // WalkFunc is the type of the function called for each route visited by Walk.
257 | // At every invocation, it is given the current route, and the current router,
258 | // and a list of ancestor routes that lead to the current route.
259 | type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
260 |
261 | func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
262 | for _, t := range r.routes {
263 | if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
264 | continue
265 | }
266 |
267 | err := walkFn(t, r, ancestors)
268 | if err == SkipRouter {
269 | continue
270 | }
271 | for _, sr := range t.matchers {
272 | if h, ok := sr.(*Router); ok {
273 | err := h.walk(walkFn, ancestors)
274 | if err != nil {
275 | return err
276 | }
277 | }
278 | }
279 | if h, ok := t.handler.(*Router); ok {
280 | ancestors = append(ancestors, t)
281 | err := h.walk(walkFn, ancestors)
282 | if err != nil {
283 | return err
284 | }
285 | ancestors = ancestors[:len(ancestors)-1]
286 | }
287 | }
288 | return nil
289 | }
290 |
291 | // ----------------------------------------------------------------------------
292 | // Context
293 | // ----------------------------------------------------------------------------
294 |
295 | // RouteMatch stores information about a matched route.
296 | type RouteMatch struct {
297 | Route *Route
298 | Handler http.Handler
299 | Vars map[string]string
300 | }
301 |
302 | type contextKey int
303 |
304 | const (
305 | varsKey contextKey = iota
306 | routeKey
307 | )
308 |
309 | // Vars returns the route variables for the current request, if any.
310 | func Vars(r *http.Request) map[string]string {
311 | if rv := context.Get(r, varsKey); rv != nil {
312 | return rv.(map[string]string)
313 | }
314 | return nil
315 | }
316 |
317 | // CurrentRoute returns the matched route for the current request, if any.
318 | // This only works when called inside the handler of the matched route
319 | // because the matched route is stored in the request context which is cleared
320 | // after the handler returns, unless the KeepContext option is set on the
321 | // Router.
322 | func CurrentRoute(r *http.Request) *Route {
323 | if rv := context.Get(r, routeKey); rv != nil {
324 | return rv.(*Route)
325 | }
326 | return nil
327 | }
328 |
329 | func setVars(r *http.Request, val interface{}) {
330 | if val != nil {
331 | context.Set(r, varsKey, val)
332 | }
333 | }
334 |
335 | func setCurrentRoute(r *http.Request, val interface{}) {
336 | if val != nil {
337 | context.Set(r, routeKey, val)
338 | }
339 | }
340 |
341 | // ----------------------------------------------------------------------------
342 | // Helpers
343 | // ----------------------------------------------------------------------------
344 |
345 | // cleanPath returns the canonical path for p, eliminating . and .. elements.
346 | // Borrowed from the net/http package.
347 | func cleanPath(p string) string {
348 | if p == "" {
349 | return "/"
350 | }
351 | if p[0] != '/' {
352 | p = "/" + p
353 | }
354 | np := path.Clean(p)
355 | // path.Clean removes trailing slash except for root;
356 | // put the trailing slash back if necessary.
357 | if p[len(p)-1] == '/' && np != "/" {
358 | np += "/"
359 | }
360 | return np
361 | }
362 |
363 | // uniqueVars returns an error if two slices contain duplicated strings.
364 | func uniqueVars(s1, s2 []string) error {
365 | for _, v1 := range s1 {
366 | for _, v2 := range s2 {
367 | if v1 == v2 {
368 | return fmt.Errorf("mux: duplicated route variable %q", v2)
369 | }
370 | }
371 | }
372 | return nil
373 | }
374 |
375 | // checkPairs returns the count of strings passed in, and an error if
376 | // the count is not an even number.
377 | func checkPairs(pairs ...string) (int, error) {
378 | length := len(pairs)
379 | if length%2 != 0 {
380 | return length, fmt.Errorf(
381 | "mux: number of parameters must be multiple of 2, got %v", pairs)
382 | }
383 | return length, nil
384 | }
385 |
386 | // mapFromPairsToString converts variadic string parameters to a
387 | // string to string map.
388 | func mapFromPairsToString(pairs ...string) (map[string]string, error) {
389 | length, err := checkPairs(pairs...)
390 | if err != nil {
391 | return nil, err
392 | }
393 | m := make(map[string]string, length/2)
394 | for i := 0; i < length; i += 2 {
395 | m[pairs[i]] = pairs[i+1]
396 | }
397 | return m, nil
398 | }
399 |
400 | // mapFromPairsToRegex converts variadic string paramers to a
401 | // string to regex map.
402 | func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
403 | length, err := checkPairs(pairs...)
404 | if err != nil {
405 | return nil, err
406 | }
407 | m := make(map[string]*regexp.Regexp, length/2)
408 | for i := 0; i < length; i += 2 {
409 | regex, err := regexp.Compile(pairs[i+1])
410 | if err != nil {
411 | return nil, err
412 | }
413 | m[pairs[i]] = regex
414 | }
415 | return m, nil
416 | }
417 |
418 | // matchInArray returns true if the given string value is in the array.
419 | func matchInArray(arr []string, value string) bool {
420 | for _, v := range arr {
421 | if v == value {
422 | return true
423 | }
424 | }
425 | return false
426 | }
427 |
428 | // matchMapWithString returns true if the given key/value pairs exist in a given map.
429 | func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
430 | for k, v := range toCheck {
431 | // Check if key exists.
432 | if canonicalKey {
433 | k = http.CanonicalHeaderKey(k)
434 | }
435 | if values := toMatch[k]; values == nil {
436 | return false
437 | } else if v != "" {
438 | // If value was defined as an empty string we only check that the
439 | // key exists. Otherwise we also check for equality.
440 | valueExists := false
441 | for _, value := range values {
442 | if v == value {
443 | valueExists = true
444 | break
445 | }
446 | }
447 | if !valueExists {
448 | return false
449 | }
450 | }
451 | }
452 | return true
453 | }
454 |
455 | // matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
456 | // the given regex
457 | func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
458 | for k, v := range toCheck {
459 | // Check if key exists.
460 | if canonicalKey {
461 | k = http.CanonicalHeaderKey(k)
462 | }
463 | if values := toMatch[k]; values == nil {
464 | return false
465 | } else if v != nil {
466 | // If value was defined as an empty string we only check that the
467 | // key exists. Otherwise we also check for equality.
468 | valueExists := false
469 | for _, value := range values {
470 | if v.MatchString(value) {
471 | valueExists = true
472 | break
473 | }
474 | }
475 | if !valueExists {
476 | return false
477 | }
478 | }
479 | }
480 | return true
481 | }
482 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/regexp.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import (
8 | "bytes"
9 | "fmt"
10 | "net/http"
11 | "net/url"
12 | "regexp"
13 | "strconv"
14 | "strings"
15 | )
16 |
17 | // newRouteRegexp parses a route template and returns a routeRegexp,
18 | // used to match a host, a path or a query string.
19 | //
20 | // It will extract named variables, assemble a regexp to be matched, create
21 | // a "reverse" template to build URLs and compile regexps to validate variable
22 | // values used in URL building.
23 | //
24 | // Previously we accepted only Python-like identifiers for variable
25 | // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
26 | // name and pattern can't be empty, and names can't contain a colon.
27 | func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
28 | // Check if it is well-formed.
29 | idxs, errBraces := braceIndices(tpl)
30 | if errBraces != nil {
31 | return nil, errBraces
32 | }
33 | // Backup the original.
34 | template := tpl
35 | // Now let's parse it.
36 | defaultPattern := "[^/]+"
37 | if matchQuery {
38 | defaultPattern = "[^?&]*"
39 | } else if matchHost {
40 | defaultPattern = "[^.]+"
41 | matchPrefix = false
42 | }
43 | // Only match strict slash if not matching
44 | if matchPrefix || matchHost || matchQuery {
45 | strictSlash = false
46 | }
47 | // Set a flag for strictSlash.
48 | endSlash := false
49 | if strictSlash && strings.HasSuffix(tpl, "/") {
50 | tpl = tpl[:len(tpl)-1]
51 | endSlash = true
52 | }
53 | varsN := make([]string, len(idxs)/2)
54 | varsR := make([]*regexp.Regexp, len(idxs)/2)
55 | pattern := bytes.NewBufferString("")
56 | pattern.WriteByte('^')
57 | reverse := bytes.NewBufferString("")
58 | var end int
59 | var err error
60 | for i := 0; i < len(idxs); i += 2 {
61 | // Set all values we are interested in.
62 | raw := tpl[end:idxs[i]]
63 | end = idxs[i+1]
64 | parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
65 | name := parts[0]
66 | patt := defaultPattern
67 | if len(parts) == 2 {
68 | patt = parts[1]
69 | }
70 | // Name or pattern can't be empty.
71 | if name == "" || patt == "" {
72 | return nil, fmt.Errorf("mux: missing name or pattern in %q",
73 | tpl[idxs[i]:end])
74 | }
75 | // Build the regexp pattern.
76 | fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
77 |
78 | // Build the reverse template.
79 | fmt.Fprintf(reverse, "%s%%s", raw)
80 |
81 | // Append variable name and compiled pattern.
82 | varsN[i/2] = name
83 | varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
84 | if err != nil {
85 | return nil, err
86 | }
87 | }
88 | // Add the remaining.
89 | raw := tpl[end:]
90 | pattern.WriteString(regexp.QuoteMeta(raw))
91 | if strictSlash {
92 | pattern.WriteString("[/]?")
93 | }
94 | if matchQuery {
95 | // Add the default pattern if the query value is empty
96 | if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
97 | pattern.WriteString(defaultPattern)
98 | }
99 | }
100 | if !matchPrefix {
101 | pattern.WriteByte('$')
102 | }
103 | reverse.WriteString(raw)
104 | if endSlash {
105 | reverse.WriteByte('/')
106 | }
107 | // Compile full regexp.
108 | reg, errCompile := regexp.Compile(pattern.String())
109 | if errCompile != nil {
110 | return nil, errCompile
111 | }
112 | // Done!
113 | return &routeRegexp{
114 | template: template,
115 | matchHost: matchHost,
116 | matchQuery: matchQuery,
117 | strictSlash: strictSlash,
118 | regexp: reg,
119 | reverse: reverse.String(),
120 | varsN: varsN,
121 | varsR: varsR,
122 | }, nil
123 | }
124 |
125 | // routeRegexp stores a regexp to match a host or path and information to
126 | // collect and validate route variables.
127 | type routeRegexp struct {
128 | // The unmodified template.
129 | template string
130 | // True for host match, false for path or query string match.
131 | matchHost bool
132 | // True for query string match, false for path and host match.
133 | matchQuery bool
134 | // The strictSlash value defined on the route, but disabled if PathPrefix was used.
135 | strictSlash bool
136 | // Expanded regexp.
137 | regexp *regexp.Regexp
138 | // Reverse template.
139 | reverse string
140 | // Variable names.
141 | varsN []string
142 | // Variable regexps (validators).
143 | varsR []*regexp.Regexp
144 | }
145 |
146 | // Match matches the regexp against the URL host or path.
147 | func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
148 | if !r.matchHost {
149 | if r.matchQuery {
150 | return r.matchQueryString(req)
151 | }
152 |
153 | return r.regexp.MatchString(req.URL.Path)
154 | }
155 |
156 | return r.regexp.MatchString(getHost(req))
157 | }
158 |
159 | // url builds a URL part using the given values.
160 | func (r *routeRegexp) url(values map[string]string) (string, error) {
161 | urlValues := make([]interface{}, len(r.varsN))
162 | for k, v := range r.varsN {
163 | value, ok := values[v]
164 | if !ok {
165 | return "", fmt.Errorf("mux: missing route variable %q", v)
166 | }
167 | urlValues[k] = value
168 | }
169 | rv := fmt.Sprintf(r.reverse, urlValues...)
170 | if !r.regexp.MatchString(rv) {
171 | // The URL is checked against the full regexp, instead of checking
172 | // individual variables. This is faster but to provide a good error
173 | // message, we check individual regexps if the URL doesn't match.
174 | for k, v := range r.varsN {
175 | if !r.varsR[k].MatchString(values[v]) {
176 | return "", fmt.Errorf(
177 | "mux: variable %q doesn't match, expected %q", values[v],
178 | r.varsR[k].String())
179 | }
180 | }
181 | }
182 | return rv, nil
183 | }
184 |
185 | // getURLQuery returns a single query parameter from a request URL.
186 | // For a URL with foo=bar&baz=ding, we return only the relevant key
187 | // value pair for the routeRegexp.
188 | func (r *routeRegexp) getURLQuery(req *http.Request) string {
189 | if !r.matchQuery {
190 | return ""
191 | }
192 | templateKey := strings.SplitN(r.template, "=", 2)[0]
193 | for key, vals := range req.URL.Query() {
194 | if key == templateKey && len(vals) > 0 {
195 | return key + "=" + vals[0]
196 | }
197 | }
198 | return ""
199 | }
200 |
201 | func (r *routeRegexp) matchQueryString(req *http.Request) bool {
202 | return r.regexp.MatchString(r.getURLQuery(req))
203 | }
204 |
205 | // braceIndices returns the first level curly brace indices from a string.
206 | // It returns an error in case of unbalanced braces.
207 | func braceIndices(s string) ([]int, error) {
208 | var level, idx int
209 | var idxs []int
210 | for i := 0; i < len(s); i++ {
211 | switch s[i] {
212 | case '{':
213 | if level++; level == 1 {
214 | idx = i
215 | }
216 | case '}':
217 | if level--; level == 0 {
218 | idxs = append(idxs, idx, i+1)
219 | } else if level < 0 {
220 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
221 | }
222 | }
223 | }
224 | if level != 0 {
225 | return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
226 | }
227 | return idxs, nil
228 | }
229 |
230 | // varGroupName builds a capturing group name for the indexed variable.
231 | func varGroupName(idx int) string {
232 | return "v" + strconv.Itoa(idx)
233 | }
234 |
235 | // ----------------------------------------------------------------------------
236 | // routeRegexpGroup
237 | // ----------------------------------------------------------------------------
238 |
239 | // routeRegexpGroup groups the route matchers that carry variables.
240 | type routeRegexpGroup struct {
241 | host *routeRegexp
242 | path *routeRegexp
243 | queries []*routeRegexp
244 | }
245 |
246 | // setMatch extracts the variables from the URL once a route matches.
247 | func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
248 | // Store host variables.
249 | if v.host != nil {
250 | host := getHost(req)
251 | matches := v.host.regexp.FindStringSubmatchIndex(host)
252 | if len(matches) > 0 {
253 | extractVars(host, matches, v.host.varsN, m.Vars)
254 | }
255 | }
256 | // Store path variables.
257 | if v.path != nil {
258 | matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path)
259 | if len(matches) > 0 {
260 | extractVars(req.URL.Path, matches, v.path.varsN, m.Vars)
261 | // Check if we should redirect.
262 | if v.path.strictSlash {
263 | p1 := strings.HasSuffix(req.URL.Path, "/")
264 | p2 := strings.HasSuffix(v.path.template, "/")
265 | if p1 != p2 {
266 | u, _ := url.Parse(req.URL.String())
267 | if p1 {
268 | u.Path = u.Path[:len(u.Path)-1]
269 | } else {
270 | u.Path += "/"
271 | }
272 | m.Handler = http.RedirectHandler(u.String(), 301)
273 | }
274 | }
275 | }
276 | }
277 | // Store query string variables.
278 | for _, q := range v.queries {
279 | queryURL := q.getURLQuery(req)
280 | matches := q.regexp.FindStringSubmatchIndex(queryURL)
281 | if len(matches) > 0 {
282 | extractVars(queryURL, matches, q.varsN, m.Vars)
283 | }
284 | }
285 | }
286 |
287 | // getHost tries its best to return the request host.
288 | func getHost(r *http.Request) string {
289 | if r.URL.IsAbs() {
290 | return r.URL.Host
291 | }
292 | host := r.Host
293 | // Slice off any port information.
294 | if i := strings.Index(host, ":"); i != -1 {
295 | host = host[:i]
296 | }
297 | return host
298 |
299 | }
300 |
301 | func extractVars(input string, matches []int, names []string, output map[string]string) {
302 | matchesCount := 0
303 | prevEnd := -1
304 | for i := 2; i < len(matches) && matchesCount < len(names); i += 2 {
305 | if prevEnd < matches[i+1] {
306 | value := input[matches[i]:matches[i+1]]
307 | output[names[matchesCount]] = value
308 | prevEnd = matches[i+1]
309 | matchesCount++
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/likes/vendor/github.com/gorilla/mux/route.go:
--------------------------------------------------------------------------------
1 | // Copyright 2012 The Gorilla Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package mux
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "net/http"
11 | "net/url"
12 | "regexp"
13 | "strings"
14 | )
15 |
16 | // Route stores information to match a request and build URLs.
17 | type Route struct {
18 | // Parent where the route was registered (a Router).
19 | parent parentRoute
20 | // Request handler for the route.
21 | handler http.Handler
22 | // List of matchers.
23 | matchers []matcher
24 | // Manager for the variables from host and path.
25 | regexp *routeRegexpGroup
26 | // If true, when the path pattern is "/path/", accessing "/path" will
27 | // redirect to the former and vice versa.
28 | strictSlash bool
29 | // If true, this route never matches: it is only used to build URLs.
30 | buildOnly bool
31 | // The name used to build URLs.
32 | name string
33 | // Error resulted from building a route.
34 | err error
35 |
36 | buildVarsFunc BuildVarsFunc
37 | }
38 |
39 | // Match matches the route against the request.
40 | func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
41 | if r.buildOnly || r.err != nil {
42 | return false
43 | }
44 | // Match everything.
45 | for _, m := range r.matchers {
46 | if matched := m.Match(req, match); !matched {
47 | return false
48 | }
49 | }
50 | // Yay, we have a match. Let's collect some info about it.
51 | if match.Route == nil {
52 | match.Route = r
53 | }
54 | if match.Handler == nil {
55 | match.Handler = r.handler
56 | }
57 | if match.Vars == nil {
58 | match.Vars = make(map[string]string)
59 | }
60 | // Set variables.
61 | if r.regexp != nil {
62 | r.regexp.setMatch(req, match, r)
63 | }
64 | return true
65 | }
66 |
67 | // ----------------------------------------------------------------------------
68 | // Route attributes
69 | // ----------------------------------------------------------------------------
70 |
71 | // GetError returns an error resulted from building the route, if any.
72 | func (r *Route) GetError() error {
73 | return r.err
74 | }
75 |
76 | // BuildOnly sets the route to never match: it is only used to build URLs.
77 | func (r *Route) BuildOnly() *Route {
78 | r.buildOnly = true
79 | return r
80 | }
81 |
82 | // Handler --------------------------------------------------------------------
83 |
84 | // Handler sets a handler for the route.
85 | func (r *Route) Handler(handler http.Handler) *Route {
86 | if r.err == nil {
87 | r.handler = handler
88 | }
89 | return r
90 | }
91 |
92 | // HandlerFunc sets a handler function for the route.
93 | func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
94 | return r.Handler(http.HandlerFunc(f))
95 | }
96 |
97 | // GetHandler returns the handler for the route, if any.
98 | func (r *Route) GetHandler() http.Handler {
99 | return r.handler
100 | }
101 |
102 | // Name -----------------------------------------------------------------------
103 |
104 | // Name sets the name for the route, used to build URLs.
105 | // If the name was registered already it will be overwritten.
106 | func (r *Route) Name(name string) *Route {
107 | if r.name != "" {
108 | r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
109 | r.name, name)
110 | }
111 | if r.err == nil {
112 | r.name = name
113 | r.getNamedRoutes()[name] = r
114 | }
115 | return r
116 | }
117 |
118 | // GetName returns the name for the route, if any.
119 | func (r *Route) GetName() string {
120 | return r.name
121 | }
122 |
123 | // ----------------------------------------------------------------------------
124 | // Matchers
125 | // ----------------------------------------------------------------------------
126 |
127 | // matcher types try to match a request.
128 | type matcher interface {
129 | Match(*http.Request, *RouteMatch) bool
130 | }
131 |
132 | // addMatcher adds a matcher to the route.
133 | func (r *Route) addMatcher(m matcher) *Route {
134 | if r.err == nil {
135 | r.matchers = append(r.matchers, m)
136 | }
137 | return r
138 | }
139 |
140 | // addRegexpMatcher adds a host or path matcher and builder to a route.
141 | func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
142 | if r.err != nil {
143 | return r.err
144 | }
145 | r.regexp = r.getRegexpGroup()
146 | if !matchHost && !matchQuery {
147 | if len(tpl) == 0 || tpl[0] != '/' {
148 | return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
149 | }
150 | if r.regexp.path != nil {
151 | tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
152 | }
153 | }
154 | rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
155 | if err != nil {
156 | return err
157 | }
158 | for _, q := range r.regexp.queries {
159 | if err = uniqueVars(rr.varsN, q.varsN); err != nil {
160 | return err
161 | }
162 | }
163 | if matchHost {
164 | if r.regexp.path != nil {
165 | if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
166 | return err
167 | }
168 | }
169 | r.regexp.host = rr
170 | } else {
171 | if r.regexp.host != nil {
172 | if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
173 | return err
174 | }
175 | }
176 | if matchQuery {
177 | r.regexp.queries = append(r.regexp.queries, rr)
178 | } else {
179 | r.regexp.path = rr
180 | }
181 | }
182 | r.addMatcher(rr)
183 | return nil
184 | }
185 |
186 | // Headers --------------------------------------------------------------------
187 |
188 | // headerMatcher matches the request against header values.
189 | type headerMatcher map[string]string
190 |
191 | func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
192 | return matchMapWithString(m, r.Header, true)
193 | }
194 |
195 | // Headers adds a matcher for request header values.
196 | // It accepts a sequence of key/value pairs to be matched. For example:
197 | //
198 | // r := mux.NewRouter()
199 | // r.Headers("Content-Type", "application/json",
200 | // "X-Requested-With", "XMLHttpRequest")
201 | //
202 | // The above route will only match if both request header values match.
203 | // If the value is an empty string, it will match any value if the key is set.
204 | func (r *Route) Headers(pairs ...string) *Route {
205 | if r.err == nil {
206 | var headers map[string]string
207 | headers, r.err = mapFromPairsToString(pairs...)
208 | return r.addMatcher(headerMatcher(headers))
209 | }
210 | return r
211 | }
212 |
213 | // headerRegexMatcher matches the request against the route given a regex for the header
214 | type headerRegexMatcher map[string]*regexp.Regexp
215 |
216 | func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
217 | return matchMapWithRegex(m, r.Header, true)
218 | }
219 |
220 | // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
221 | // support. For example:
222 | //
223 | // r := mux.NewRouter()
224 | // r.HeadersRegexp("Content-Type", "application/(text|json)",
225 | // "X-Requested-With", "XMLHttpRequest")
226 | //
227 | // The above route will only match if both the request header matches both regular expressions.
228 | // It the value is an empty string, it will match any value if the key is set.
229 | func (r *Route) HeadersRegexp(pairs ...string) *Route {
230 | if r.err == nil {
231 | var headers map[string]*regexp.Regexp
232 | headers, r.err = mapFromPairsToRegex(pairs...)
233 | return r.addMatcher(headerRegexMatcher(headers))
234 | }
235 | return r
236 | }
237 |
238 | // Host -----------------------------------------------------------------------
239 |
240 | // Host adds a matcher for the URL host.
241 | // It accepts a template with zero or more URL variables enclosed by {}.
242 | // Variables can define an optional regexp pattern to be matched:
243 | //
244 | // - {name} matches anything until the next dot.
245 | //
246 | // - {name:pattern} matches the given regexp pattern.
247 | //
248 | // For example:
249 | //
250 | // r := mux.NewRouter()
251 | // r.Host("www.example.com")
252 | // r.Host("{subdomain}.domain.com")
253 | // r.Host("{subdomain:[a-z]+}.domain.com")
254 | //
255 | // Variable names must be unique in a given route. They can be retrieved
256 | // calling mux.Vars(request).
257 | func (r *Route) Host(tpl string) *Route {
258 | r.err = r.addRegexpMatcher(tpl, true, false, false)
259 | return r
260 | }
261 |
262 | // MatcherFunc ----------------------------------------------------------------
263 |
264 | // MatcherFunc is the function signature used by custom matchers.
265 | type MatcherFunc func(*http.Request, *RouteMatch) bool
266 |
267 | // Match returns the match for a given request.
268 | func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
269 | return m(r, match)
270 | }
271 |
272 | // MatcherFunc adds a custom function to be used as request matcher.
273 | func (r *Route) MatcherFunc(f MatcherFunc) *Route {
274 | return r.addMatcher(f)
275 | }
276 |
277 | // Methods --------------------------------------------------------------------
278 |
279 | // methodMatcher matches the request against HTTP methods.
280 | type methodMatcher []string
281 |
282 | func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
283 | return matchInArray(m, r.Method)
284 | }
285 |
286 | // Methods adds a matcher for HTTP methods.
287 | // It accepts a sequence of one or more methods to be matched, e.g.:
288 | // "GET", "POST", "PUT".
289 | func (r *Route) Methods(methods ...string) *Route {
290 | for k, v := range methods {
291 | methods[k] = strings.ToUpper(v)
292 | }
293 | return r.addMatcher(methodMatcher(methods))
294 | }
295 |
296 | // Path -----------------------------------------------------------------------
297 |
298 | // Path adds a matcher for the URL path.
299 | // It accepts a template with zero or more URL variables enclosed by {}. The
300 | // template must start with a "/".
301 | // Variables can define an optional regexp pattern to be matched:
302 | //
303 | // - {name} matches anything until the next slash.
304 | //
305 | // - {name:pattern} matches the given regexp pattern.
306 | //
307 | // For example:
308 | //
309 | // r := mux.NewRouter()
310 | // r.Path("/products/").Handler(ProductsHandler)
311 | // r.Path("/products/{key}").Handler(ProductsHandler)
312 | // r.Path("/articles/{category}/{id:[0-9]+}").
313 | // Handler(ArticleHandler)
314 | //
315 | // Variable names must be unique in a given route. They can be retrieved
316 | // calling mux.Vars(request).
317 | func (r *Route) Path(tpl string) *Route {
318 | r.err = r.addRegexpMatcher(tpl, false, false, false)
319 | return r
320 | }
321 |
322 | // PathPrefix -----------------------------------------------------------------
323 |
324 | // PathPrefix adds a matcher for the URL path prefix. This matches if the given
325 | // template is a prefix of the full URL path. See Route.Path() for details on
326 | // the tpl argument.
327 | //
328 | // Note that it does not treat slashes specially ("/foobar/" will be matched by
329 | // the prefix "/foo") so you may want to use a trailing slash here.
330 | //
331 | // Also note that the setting of Router.StrictSlash() has no effect on routes
332 | // with a PathPrefix matcher.
333 | func (r *Route) PathPrefix(tpl string) *Route {
334 | r.err = r.addRegexpMatcher(tpl, false, true, false)
335 | return r
336 | }
337 |
338 | // Query ----------------------------------------------------------------------
339 |
340 | // Queries adds a matcher for URL query values.
341 | // It accepts a sequence of key/value pairs. Values may define variables.
342 | // For example:
343 | //
344 | // r := mux.NewRouter()
345 | // r.Queries("foo", "bar", "id", "{id:[0-9]+}")
346 | //
347 | // The above route will only match if the URL contains the defined queries
348 | // values, e.g.: ?foo=bar&id=42.
349 | //
350 | // It the value is an empty string, it will match any value if the key is set.
351 | //
352 | // Variables can define an optional regexp pattern to be matched:
353 | //
354 | // - {name} matches anything until the next slash.
355 | //
356 | // - {name:pattern} matches the given regexp pattern.
357 | func (r *Route) Queries(pairs ...string) *Route {
358 | length := len(pairs)
359 | if length%2 != 0 {
360 | r.err = fmt.Errorf(
361 | "mux: number of parameters must be multiple of 2, got %v", pairs)
362 | return nil
363 | }
364 | for i := 0; i < length; i += 2 {
365 | if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil {
366 | return r
367 | }
368 | }
369 |
370 | return r
371 | }
372 |
373 | // Schemes --------------------------------------------------------------------
374 |
375 | // schemeMatcher matches the request against URL schemes.
376 | type schemeMatcher []string
377 |
378 | func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
379 | return matchInArray(m, r.URL.Scheme)
380 | }
381 |
382 | // Schemes adds a matcher for URL schemes.
383 | // It accepts a sequence of schemes to be matched, e.g.: "http", "https".
384 | func (r *Route) Schemes(schemes ...string) *Route {
385 | for k, v := range schemes {
386 | schemes[k] = strings.ToLower(v)
387 | }
388 | return r.addMatcher(schemeMatcher(schemes))
389 | }
390 |
391 | // BuildVarsFunc --------------------------------------------------------------
392 |
393 | // BuildVarsFunc is the function signature used by custom build variable
394 | // functions (which can modify route variables before a route's URL is built).
395 | type BuildVarsFunc func(map[string]string) map[string]string
396 |
397 | // BuildVarsFunc adds a custom function to be used to modify build variables
398 | // before a route's URL is built.
399 | func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
400 | r.buildVarsFunc = f
401 | return r
402 | }
403 |
404 | // Subrouter ------------------------------------------------------------------
405 |
406 | // Subrouter creates a subrouter for the route.
407 | //
408 | // It will test the inner routes only if the parent route matched. For example:
409 | //
410 | // r := mux.NewRouter()
411 | // s := r.Host("www.example.com").Subrouter()
412 | // s.HandleFunc("/products/", ProductsHandler)
413 | // s.HandleFunc("/products/{key}", ProductHandler)
414 | // s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
415 | //
416 | // Here, the routes registered in the subrouter won't be tested if the host
417 | // doesn't match.
418 | func (r *Route) Subrouter() *Router {
419 | router := &Router{parent: r, strictSlash: r.strictSlash}
420 | r.addMatcher(router)
421 | return router
422 | }
423 |
424 | // ----------------------------------------------------------------------------
425 | // URL building
426 | // ----------------------------------------------------------------------------
427 |
428 | // URL builds a URL for the route.
429 | //
430 | // It accepts a sequence of key/value pairs for the route variables. For
431 | // example, given this route:
432 | //
433 | // r := mux.NewRouter()
434 | // r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
435 | // Name("article")
436 | //
437 | // ...a URL for it can be built using:
438 | //
439 | // url, err := r.Get("article").URL("category", "technology", "id", "42")
440 | //
441 | // ...which will return an url.URL with the following path:
442 | //
443 | // "/articles/technology/42"
444 | //
445 | // This also works for host variables:
446 | //
447 | // r := mux.NewRouter()
448 | // r.Host("{subdomain}.domain.com").
449 | // HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
450 | // Name("article")
451 | //
452 | // // url.String() will be "http://news.domain.com/articles/technology/42"
453 | // url, err := r.Get("article").URL("subdomain", "news",
454 | // "category", "technology",
455 | // "id", "42")
456 | //
457 | // All variables defined in the route are required, and their values must
458 | // conform to the corresponding patterns.
459 | func (r *Route) URL(pairs ...string) (*url.URL, error) {
460 | if r.err != nil {
461 | return nil, r.err
462 | }
463 | if r.regexp == nil {
464 | return nil, errors.New("mux: route doesn't have a host or path")
465 | }
466 | values, err := r.prepareVars(pairs...)
467 | if err != nil {
468 | return nil, err
469 | }
470 | var scheme, host, path string
471 | if r.regexp.host != nil {
472 | // Set a default scheme.
473 | scheme = "http"
474 | if host, err = r.regexp.host.url(values); err != nil {
475 | return nil, err
476 | }
477 | }
478 | if r.regexp.path != nil {
479 | if path, err = r.regexp.path.url(values); err != nil {
480 | return nil, err
481 | }
482 | }
483 | return &url.URL{
484 | Scheme: scheme,
485 | Host: host,
486 | Path: path,
487 | }, nil
488 | }
489 |
490 | // URLHost builds the host part of the URL for a route. See Route.URL().
491 | //
492 | // The route must have a host defined.
493 | func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
494 | if r.err != nil {
495 | return nil, r.err
496 | }
497 | if r.regexp == nil || r.regexp.host == nil {
498 | return nil, errors.New("mux: route doesn't have a host")
499 | }
500 | values, err := r.prepareVars(pairs...)
501 | if err != nil {
502 | return nil, err
503 | }
504 | host, err := r.regexp.host.url(values)
505 | if err != nil {
506 | return nil, err
507 | }
508 | return &url.URL{
509 | Scheme: "http",
510 | Host: host,
511 | }, nil
512 | }
513 |
514 | // URLPath builds the path part of the URL for a route. See Route.URL().
515 | //
516 | // The route must have a path defined.
517 | func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
518 | if r.err != nil {
519 | return nil, r.err
520 | }
521 | if r.regexp == nil || r.regexp.path == nil {
522 | return nil, errors.New("mux: route doesn't have a path")
523 | }
524 | values, err := r.prepareVars(pairs...)
525 | if err != nil {
526 | return nil, err
527 | }
528 | path, err := r.regexp.path.url(values)
529 | if err != nil {
530 | return nil, err
531 | }
532 | return &url.URL{
533 | Path: path,
534 | }, nil
535 | }
536 |
537 | // GetPathTemplate returns the template used to build the
538 | // route match.
539 | // This is useful for building simple REST API documentation and for instrumentation
540 | // against third-party services.
541 | // An error will be returned if the route does not define a path.
542 | func (r *Route) GetPathTemplate() (string, error) {
543 | if r.err != nil {
544 | return "", r.err
545 | }
546 | if r.regexp == nil || r.regexp.path == nil {
547 | return "", errors.New("mux: route doesn't have a path")
548 | }
549 | return r.regexp.path.template, nil
550 | }
551 |
552 | // GetHostTemplate returns the template used to build the
553 | // route match.
554 | // This is useful for building simple REST API documentation and for instrumentation
555 | // against third-party services.
556 | // An error will be returned if the route does not define a host.
557 | func (r *Route) GetHostTemplate() (string, error) {
558 | if r.err != nil {
559 | return "", r.err
560 | }
561 | if r.regexp == nil || r.regexp.host == nil {
562 | return "", errors.New("mux: route doesn't have a host")
563 | }
564 | return r.regexp.host.template, nil
565 | }
566 |
567 | // prepareVars converts the route variable pairs into a map. If the route has a
568 | // BuildVarsFunc, it is invoked.
569 | func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
570 | m, err := mapFromPairsToString(pairs...)
571 | if err != nil {
572 | return nil, err
573 | }
574 | return r.buildVars(m), nil
575 | }
576 |
577 | func (r *Route) buildVars(m map[string]string) map[string]string {
578 | if r.parent != nil {
579 | m = r.parent.buildVars(m)
580 | }
581 | if r.buildVarsFunc != nil {
582 | m = r.buildVarsFunc(m)
583 | }
584 | return m
585 | }
586 |
587 | // ----------------------------------------------------------------------------
588 | // parentRoute
589 | // ----------------------------------------------------------------------------
590 |
591 | // parentRoute allows routes to know about parent host and path definitions.
592 | type parentRoute interface {
593 | getNamedRoutes() map[string]*Route
594 | getRegexpGroup() *routeRegexpGroup
595 | buildVars(map[string]string) map[string]string
596 | }
597 |
598 | // getNamedRoutes returns the map where named routes are registered.
599 | func (r *Route) getNamedRoutes() map[string]*Route {
600 | if r.parent == nil {
601 | // During tests router is not always set.
602 | r.parent = NewRouter()
603 | }
604 | return r.parent.getNamedRoutes()
605 | }
606 |
607 | // getRegexpGroup returns regexp definitions from this route.
608 | func (r *Route) getRegexpGroup() *routeRegexpGroup {
609 | if r.regexp == nil {
610 | if r.parent == nil {
611 | // During tests router is not always set.
612 | r.parent = NewRouter()
613 | }
614 | regexp := r.parent.getRegexpGroup()
615 | if regexp == nil {
616 | r.regexp = new(routeRegexpGroup)
617 | } else {
618 | // Copy.
619 | r.regexp = &routeRegexpGroup{
620 | host: regexp.host,
621 | path: regexp.path,
622 | queries: regexp.queries,
623 | }
624 | }
625 | }
626 | return r.regexp
627 | }
628 |
--------------------------------------------------------------------------------
/push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | docker-compose build
6 | docker-compose push
7 | docker save -o images.tar \
8 | dockerdemos/lab-web \
9 | dockerdemos/lab-words-dispatcher \
10 | dockerdemos/lab-words-java \
11 | mongo-express:0.31.0 \
12 | mongo:3.3.15
13 |
--------------------------------------------------------------------------------
/web/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:1.11.4-alpine
2 |
3 | COPY nginx.conf /etc/nginx/nginx.conf
4 | COPY default.conf /etc/nginx/conf.d/default.conf
5 | COPY static /usr/share/nginx/html
6 |
--------------------------------------------------------------------------------
/web/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 |
5 | location / {
6 | root /usr/share/nginx/html;
7 | index index.html index.htm;
8 | }
9 |
10 | location ~ /words/(.*) {
11 | resolver 127.0.0.11 ipv6=off valid=1s;
12 | set $upstream words;
13 |
14 | proxy_pass http://$upstream:8080/$1;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/web/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 | worker_processes 1;
3 |
4 | error_log stderr debug;
5 | pid /var/run/nginx.pid;
6 |
7 | events {
8 | worker_connections 1024;
9 | }
10 |
11 | http {
12 | include /etc/nginx/mime.types;
13 | default_type application/octet-stream;
14 |
15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16 | '$status $body_bytes_sent "$http_referer" '
17 | '"$http_user_agent" "$http_x_forwarded_for"';
18 |
19 | access_log /dev/stdout;
20 |
21 | sendfile on;
22 | keepalive_timeout 65;
23 |
24 | include /etc/nginx/conf.d/*.conf;
25 | }
--------------------------------------------------------------------------------
/web/static/IndieFlower.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeStory/lab-docker/7a8e12822aadc28aa91e3c4c59e6cb044f2229d9/web/static/IndieFlower.ttf
--------------------------------------------------------------------------------
/web/static/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var lab = angular.module('lab', []);
4 |
5 | lab.controller('LabCtrl', function ($scope, $http) {
6 | $scope.noun1 = "";
7 | $scope.noun2 = "";
8 | $scope.adjective1 = "";
9 | $scope.adjective2 = "";
10 | $scope.verb = "";
11 |
12 | $http.get('/words/noun1').success(function(data) {
13 | $scope.noun1 = data;
14 | });
15 | $http.get('/words/noun2').success(function(data) {
16 | data.word = data.word + "."
17 | $scope.noun2 = data;
18 | });
19 | $http.get('/words/adjective1').success(function(data) {
20 | data.word = data.word.charAt(0).toUpperCase() + data.word.substr(1)
21 | $scope.adjective1 = data;
22 | });
23 | $http.get('/words/adjective2').success(function(data) {
24 | $scope.adjective2 = data;
25 | });
26 | $http.get('/words/verb').success(function(data) {
27 | $scope.verb = data;
28 | });
29 |
30 | $scope.share = function() {
31 | var sentence = $scope.noun1 + " " + $scope.adjective1 + " " + $scope.verb + " " + $scope.noun2 + " " + $scope.adjective2
32 | $http.post('http://docker.local:9000/likes',{'name':sentence})
33 | };
34 | });
35 |
--------------------------------------------------------------------------------
/web/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Docker Lab - Cadavres exquis
6 |
7 |
8 |
9 |
10 |