summaryrefslogtreecommitdiff
path: root/ewww.md
blob: 8e60f8ba45819a75f3244429634597124b5d2489 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# Introduction

Ewww is a web server for static sites. It aims to be simple code,
simple to configure, simple to keep running, and fast.

## Use cases

* I have files in a directory, and a domain name pointing at the host.
  I want to serve the files using HTTPS. I want the TLS certificate to
  come from Let's Encrypt, and get renewed automatically.

* Same, but I have multiple domain names and each should serve from
  different directories and potentially have their own certificates.

* Same, but some of the domain names are aliases for each other, and
  web clients should be redirected to the main one.


# Requirements

These are main, highlevel requirements. Detailed requirements are
expressed as _scenarios_ in the acceptance criteria chapter.

* Fast, at least 100 requests per second over localhost, using HTTPS,
  on my Thinkpad T480 laptop. A self-signed certificate is OK.
* Fast, time from starting server to having served first HTTPS request
  should be at most 100 ms.
* Serves only HTTPS, except what Let's Encrypt needs to be served over
  plain HTTP.

I don't need flexibility, and I don't want to configure anything
that's not essential for this. Hardcoded assumptions are A-OK, if my
life as someone running the program is easier.

At this point, I don't need support for `If-Modified-Since` or `ETag`.
or generating directory listings. I don't even care about MIME types
for now. Those will probably become important once I start using this
software for real, but for now I am trying to keep requirements
minimal.


# Architecture

This is a thin layer on top of the Rust warp crate. It does minimal
processing for each request, and does not cache anything. 

At startup, the server is provided with a directory and it reads all
configuration files in that directory. Each configuration file looks
like this:

~~~yaml
webroot: /srv/http/example.com
hosts:
  - example.com
  - www.example.com
ports:
  http: 80, 8080, 8888
  https: 443, 4433
tls-cert: /etc/letsencrypt/live/certname/fullchain.pem
tls-key: /etc/letsencrypt/live/certname/privkey.pem
~~~

The hosts are aliases; the first host on the list is the main one, the
others automatically redirect to it.

The server is started as `root`, reads in the TLS private key and
cert, binds to the ports, then drops privileges to `nobody`. The
configuration specifies for each port if plain HTTP or HTTPS is
expected.

The server automatically listens on both port 80 (http) and 443
(https) so that it can serve the Let's Encrypt files. It only serves
the `/.well-known/` path prefix in the webroot on port 80. Everything
else gets redirected to 443. I don't think I need to serve other
ports, but it's a handy feature to have for testing, so it shall be
supported at least for testing.

There is no "reload configuration". The server needs to be restarted.
This is good enough for me, but may not be good enough for more
serious use on sites with much traffic. Restarting should be fast.

Only the GET and HEAD methods are supported for HTTP: this is a server
for static content only. Every other method returns an error.


# Acceptance criteria

## Minimal smoke test

~~~scenario
given a self-signed certificate as snakeoil.pem, using key snakeoil.key
and a running server using config file minimal.yaml
when I request GET https://example.com/
then I get status code 200
~~~

~~~{#minimal.yaml .file .yaml}
tls_key: snakeoil.key
tls_cert: snakeoil.pem
~~~


## Smoke test

~~~scenario
given a self-signed certificate as snakeoil.pem, using key snakeoil.key
and a running server using config file smoke.yaml
when I create webroot/foo with "hello, world"
and I request GET https://example.com/foo
then I get status code 200
and header content-type is "text/plain"
and body is "hello, world\n"
~~~

~~~scenario-disabled
given a self-signed certificate as snakeoil.pem, using key snakeoil.key
and a running server using config file smoke.yaml

when I request GET http://example.com/
then I am redirected to https://example.com/

when I request GET http://example.com/.well-known/foo
then I get status code 404

when I create webroot/foo
and I request GET http://example.com/.well-known/foo
then I get status code 200
and content-type is "application/octet-stream"

when I request HEAD http://example.com/.well-known/foo
then I get status code 200

when I request GET https://example.com/
then I get status code 200

when I request HEAD https://example.com/
then I get status code 200

when I request GET https://www.example.com/
then I am redirected to https://example.com/
~~~

The following config file does not specify port numbers. The test
scaffolding adds randomly chosen port numbers so that the test can run
without being root.

~~~{#smoke.yaml .file .yaml .numberLines}
tls_cert: snakeoil.pem
tls_key: snakeoil.key
~~~

## Performance test

~~~scenario-disabled
given a self-signed certificate as snakeoil.pem, using key snakeoil.key
and a running server using config file smoke.yaml
and 1000 files in webroot
when I request files under https://example.com in random order 100000 times
then I can do at least 100 requests per second
~~~

## Using POST, PUT, or DELETE fails

~~~scenario-disabled
given a self-signed certificate as snakeoil.pem, using key snakeoil.key
and a running server using config file smoke.yaml

when I request POST https://example.com/
then I get status code 405
and allow is "GET HEAD"

when I request PUT https://example.com/
then I get status code 405
and allow is "GET HEAD"

when I request DELETE https://example.com/
then I get status code 405
and allow is "GET HEAD"
~~~


---
title: "Ewww — a Web server for static sites"
author: Lars Wirzenius
bindings: ewww.yaml
functions: ewww.py
classes:
  - scenario-disabled
...