Skip to content

Commit 8d1758c

Browse files
committed
first pass at README for http-auth
1 parent 74edcae commit 8d1758c

File tree

1 file changed

+336
-1
lines changed

1 file changed

+336
-1
lines changed

http-auth/README.md

Lines changed: 336 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,339 @@ Learning objectives:
1919

2020
## Project
2121

22-
TODO
22+
[Create a new go module](https://go.dev/doc/tutorial/create-module) in this `http-auth` directory: `go mod init http-auth`.
23+
24+
Create empty main package `main.go` and main function. Check it's all working by running the app: `go run .`.
25+
26+
The main library you'll be working with is built-in to Go: `net/http`. Import it for use: `import "net/http"`.
27+
28+
Here's a basic server that you'll build from:
29+
30+
```go
31+
func main() {
32+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
33+
w.Write([]byte("Hello, world"))
34+
})
35+
36+
http.ListenAndServe(":8080", nil)
37+
}
38+
```
39+
40+
Use `curl` to interact with it: `curl -i http://localhost:8080/`
41+
42+
curl is a tool for transfering data from or to a server. It's very useful for testing and interacting with servers that you build.
43+
44+
Using `curl -i` will show you how the server responds, including the response "headers" and "body". The headers contain metadata about the response, such as what type of data is being sent back.
45+
46+
```
47+
> curl -i http://localhost:8080/
48+
HTTP/1.1 200 OK
49+
Date: Sat, 25 Jun 2022 11:17:17 GMT
50+
Content-Length: 25
51+
Content-Type: text/plain; charset=utf-8
52+
53+
Hello, world
54+
```
55+
56+
A common [protocol](https://en.m.wikipedia.org/wiki/Communication_protocol) for sending data between clients and servers over the internet is HTTP. It's used for websites, for example.
57+
58+
You can read [lots about HTTP here](https://developer.mozilla.org/en-US/docs/Web/HTTP).
59+
60+
HTTP requests are sent from a client to a server. They come in various types such as `GET`, for reading information, and `POST` for sending information back.
61+
62+
HTTP responses — data sent back to a "client" from a "server" as a result of an HTTP request — can use a set of [standard codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) to indicate the status of the server or something about the request.
63+
64+
You're going to make a server that responds to `GET` requests with some of the common ones when a client makes a request to the appropriate URL: 200, 404, 500.
65+
66+
Update your go code so that each of the following works. Notice how the URL matches the code that is returned:
67+
68+
- `/200` -> 200 OK
69+
- `/404` -> 404 Not found
70+
- `/500` -> 500 Internal Server Error
71+
72+
```
73+
> curl -i http://localhost:8080/200
74+
HTTP/1.1 200 OK
75+
Date: Sat, 25 Jun 2022 11:16:17 GMT
76+
Content-Length: 3
77+
Content-Type: text/plain; charset=utf-8
78+
79+
200
80+
81+
> curl -i http://localhost:8080/500
82+
HTTP/1.1 500 Internal Server Error
83+
Date: Sat, 25 Jun 2022 11:16:30 GMT
84+
Content-Length: 21
85+
Content-Type: text/plain; charset=utf-8
86+
87+
Internal server error
88+
89+
> curl -i http://localhost:8080/404
90+
HTTP/1.1 404 Not Found
91+
Content-Type: text/plain; charset=utf-8
92+
X-Content-Type-Options: nosniff
93+
Date: Sat, 25 Jun 2022 11:17:29 GMT
94+
Content-Length: 19
95+
96+
404 page not found
97+
```
98+
99+
Use `http.NotFoundHandler()` for the `404` error.
100+
101+
HTTP requests can return more than just plan text. Next, make the index page at `/` returns some HTML to a `GET` request. Make sure the `Content-Type` response header is set: `w.Header().Add("Content-Type", "text/html")`
102+
103+
```
104+
> curl -i http://localhost:8080/
105+
HTTP/1.1 200 OK
106+
Content-Type: text/html
107+
Date: Sun, 24 Jul 2022 09:42:30 GMT
108+
Content-Length: 42
109+
110+
<!DOCTYPE html><html><em>Hello, world</em>
111+
```
112+
113+
Now make the index page accept `POST` requests with some HTML, and return that HTML. You'll need to check the request method: `request.Method`.
114+
115+
```
116+
> curl -i -d "<em>Hi</em>" http://localhost:8080/
117+
HTTP/1.1 200 OK
118+
Content-Type: text/html
119+
Date: Sun, 24 Jul 2022 09:43:20 GMT
120+
Content-Length: 32
121+
122+
<!DOCTYPE html><html><em>Hi</em>
123+
```
124+
125+
HTTP requests can also supply "query parameters" in the URL: `/blog-posts?after=2022-05-04`. Make the handler at `/` output the query parameters as a list. Having the output spaced over multiple lines is optional, but done here for readability.
126+
127+
```
128+
> curl -i http://localhost:8080\?foo=bar
129+
HTTP/1.1 200 OK
130+
Content-Type: text/html
131+
Date: Sun, 24 Jul 2022 09:55:33 GMT
132+
Content-Length: 96
133+
134+
<!DOCTYPE html>
135+
<html>
136+
<em>Hello, world</em>
137+
<p>Query parameters:
138+
<ul>
139+
<li>foo: [bar]</li>
140+
</ul>
141+
```
142+
143+
Try putting some HTML into the query params or body. You'll see that it is interpreted as HTML:
144+
145+
```
146+
> curl -i http://localhost:8080\?foo=\<strong\>bar\</strong
147+
\>
148+
HTTP/1.1 200 OK
149+
Content-Type: text/html
150+
Date: Sun, 24 Jul 2022 09:57:20 GMT
151+
Content-Length: 113
152+
153+
<!DOCTYPE html>
154+
<html>
155+
<em>Hello, world</em>
156+
<p>Query parameters:
157+
<ul>
158+
<li>foo: [<strong>bar</strong>]</li>
159+
</ul>
160+
```
161+
162+
This isn't good! This kind of thing can lead to security issues. Search for "XSS attack" to find out more. Let's fix it.
163+
164+
"Escape" the string any time you take some input (data in `POST` or query parameters) and output it back. You'll need to investigate `html.EscapeString(v)`:
165+
166+
```
167+
> curl -i http://localhost:8080\?foo=\<strong\>bar\</strong\>
168+
HTTP/1.1 200 OK
169+
Content-Type: text/html
170+
Date: Sun, 24 Jul 2022 10:08:08 GMT
171+
Content-Length: 125
172+
173+
<!DOCTYPE html>
174+
<html>
175+
<em>Hello, world</em>
176+
<p>Query parameters:
177+
<ul>
178+
<li>foo: [&lt;strong&gt;bar&lt;/strong&gt;]</li>
179+
</ul>
180+
```
181+
182+
```
183+
> curl -i -d "<em>Hi</em>" http://localhost:8080/
184+
HTTP/1.1 200 OK
185+
Content-Type: text/html
186+
Date: Sun, 24 Jul 2022 10:08:21 GMT
187+
Content-Length: 46
188+
189+
<!DOCTYPE html>
190+
<html>
191+
&lt;em&gt;Hi&lt;/em&gt;
192+
```
193+
194+
Next you're going to add a URL that can only be accessed if you know a username and secret password.
195+
196+
Add an endpoint `/authenticated` that requires the use of HTTP Basic auth. It should return a `401 Unauthorized` status code with a `WWW-Authenticate` header if basic auth is not present or does not match a username and password of your choice. Once Basic Auth is provided, it should respond successful!
197+
198+
Go's `http` library comes with some Basic Auth support built-in, so be sure to use it to make the following work:
199+
200+
```
201+
> curl -i http://localhost:8080/authenticated
202+
HTTP/1.1 401 Unauthorized
203+
Www-Authenticate: Basic realm="localhost", charset="UTF-8"
204+
Date: Sun, 24 Jul 2022 14:12:35 GMT
205+
Content-Length: 0
206+
```
207+
208+
```
209+
> curl -i http://localhost:8080/authenticated -H 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ='
210+
HTTP/1.1 200 OK
211+
Content-Type: text/html
212+
Date: Sun, 24 Jul 2022 14:13:04 GMT
213+
Content-Length: 38
214+
215+
<!DOCTYPE html>
216+
<html>
217+
Hello username!
218+
```
219+
220+
You can generate the `dXNl...` text [using this website](https://opinionatedgeek.com/Codecs/Base64Encoder). This is "base64 encoded" which you can search for to find a bit more about. Enter `username:password` to get `dXNlcm5hbWU6cGFzc3dvcmQ=`.
221+
222+
It's not a good idea to put secrets like passwords into code. So remove any hard-coded usernames and passwords for basic auth, and use `os.Getenv(...)` so that this works:
223+
224+
```
225+
> AUTH_USERNAME=admin AUTH_PASSWORD=long-memorable-password go run .
226+
```
227+
228+
Next you're going to test how many requests your server can support, and add basic [rate limiting](https://www.cloudflare.com/en-gb/learning/bots/what-is-rate-limiting).
229+
230+
[Follow this guide](https://www.datadoghq.com/blog/apachebench/) to install and use ApacheBench, which will test to see how many requests your server can handle
231+
232+
```
233+
> ab -n 10000 -c 100 http://localhost:8080/
234+
235+
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
236+
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
237+
Licensed to The Apache Software Foundation, http://www.apache.org/
238+
239+
Benchmarking localhost (be patient)
240+
Completed 1000 requests
241+
Completed 2000 requests
242+
Completed 3000 requests
243+
Completed 4000 requests
244+
Completed 5000 requests
245+
Completed 6000 requests
246+
Completed 7000 requests
247+
Completed 8000 requests
248+
Completed 9000 requests
249+
Completed 10000 requests
250+
Finished 10000 requests
251+
252+
253+
Server Software:
254+
Server Hostname: localhost
255+
Server Port: 8080
256+
257+
Document Path: /
258+
Document Length: 76 bytes
259+
260+
Concurrency Level: 100
261+
Time taken for tests: 0.779 seconds
262+
Complete requests: 10000
263+
Failed requests: 0
264+
Total transferred: 1770000 bytes
265+
HTML transferred: 760000 bytes
266+
Requests per second: 12837.71 [#/sec] (mean)
267+
Time per request: 7.790 [ms] (mean)
268+
Time per request: 0.078 [ms] (mean, across all concurrent requests)
269+
Transfer rate: 2219.02 [Kbytes/sec] received
270+
271+
Connection Times (ms)
272+
min mean[+/-sd] median max
273+
Connect: 0 4 3.2 3 49
274+
Processing: 1 4 3.2 4 49
275+
Waiting: 0 4 3.1 4 49
276+
Total: 5 8 4.5 7 53
277+
278+
Percentage of the requests served within a certain time (ms)
279+
50% 7
280+
66% 8
281+
75% 8
282+
80% 8
283+
90% 8
284+
95% 9
285+
98% 10
286+
99% 11
287+
100% 53 (longest request)
288+
```
289+
290+
It's better to protect your server from being asked to handle too many requests than to have it fall over! So use the `rate` library to reject excess requests (> X per second) with a `503 Service Unavailable` error on a `/limited` endpoint.
291+
292+
```
293+
> go get -u golang.org/x/time
294+
```
295+
296+
You will need to import the module:
297+
298+
```go
299+
import "golang.org/x/time/rate"
300+
```
301+
302+
Then create a limiter:
303+
304+
```go
305+
lim := rate.NewLimiter(100, 30)
306+
```
307+
308+
And use it:
309+
310+
```go
311+
if lim.Allow() {
312+
// Respond as normal!
313+
} else {
314+
// Respond with an error
315+
}
316+
```
317+
318+
If it is working, you will see `Non-2xx responses` and `Failed requests` in your ApacheBench output:
319+
320+
```
321+
> ab -n 100 -c 100 http://localhost:8080/limited
322+
...
323+
324+
Document Path: /limited
325+
Document Length: 35 bytes
326+
327+
Concurrency Level: 100
328+
Time taken for tests: 0.006 seconds
329+
Complete requests: 100
330+
Failed requests: 70 <----- HERE!
331+
(Connect: 0, Receive: 0, Length: 70, Exceptions: 0)
332+
Non-2xx responses: 70 <----- HERE!
333+
Total transferred: 17170 bytes
334+
HTML transferred: 2450 bytes
335+
Requests per second: 15544.85 [#/sec] (mean)
336+
Time per request: 6.433 [ms] (mean)
337+
Time per request: 0.064 [ms] (mean, across all concurrent requests)
338+
Transfer rate: 2606.49 [Kbytes/sec] received
339+
340+
Connection Times (ms)
341+
min mean[+/-sd] median max
342+
Connect: 0 2 0.7 2 4
343+
Processing: 1 1 0.3 1 4
344+
Waiting: 0 1 0.2 1 1
345+
Total: 2 4 0.7 4 5
346+
347+
Percentage of the requests served within a certain time (ms)
348+
50% 4
349+
66% 4
350+
75% 4
351+
80% 4
352+
90% 5
353+
95% 5
354+
98% 5
355+
99% 5
356+
100% 5 (longest request)
357+
```

0 commit comments

Comments
 (0)