tangled
alpha
login
or
join now
keii.dev
/
wisp
3
fork
atom
🧚 A practical web framework for Gleam
3
fork
atom
overview
issues
pulls
pipelines
Static file serving
Louis Pilfold
2 years ago
c758f052
ab4bb5e0
+333
-29
7 changed files
expand all
collapse all
unified
split
action
manifest.toml
priv
static
styles.css
src
action
web.gleam
action.gleam
framework
gleam.toml
manifest.toml
src
framework.gleam
+5
-4
action/manifest.toml
···
3
3
4
4
packages = [
5
5
{ name = "esqlite", version = "0.8.6", build_tools = ["rebar3"], requirements = [], otp_app = "esqlite", source = "hex", outer_checksum = "607E45F4DA42601D8F530979417F57A4CD629AB49085891849302057E68EA188" },
6
6
-
{ name = "framework", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist"], source = "local", path = "../framework" },
6
6
+
{ name = "framework", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "mist", "simplifile"], source = "local", path = "../framework" },
7
7
{ name = "gleam_erlang", version = "0.20.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "F216A80C8FDFF774447B494D5E08AE4E9A911E971727B9A78FEBF5F300914584" },
8
8
{ name = "gleam_http", version = "3.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "B6EB76D304E0E66267485983E6B7BC28F3BFA6795BB2BF90FC411F6903AF6A1A" },
9
9
-
{ name = "gleam_otp", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E31B158857E3D2AF946FE6E90E0CB21699AF226F4630E93FBEAC5DB4515F8920" },
9
9
+
{ name = "gleam_otp", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E31B158857E3D2AF946FE6E90E0CB21699AF226F4630E93FBEAC5DB4515F8920" },
10
10
{ name = "gleam_stdlib", version = "0.30.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "03710B3DA047A3683117591707FCA19D32B980229DD8CE8B0603EB5B5144F6C3" },
11
11
{ name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" },
12
12
-
{ name = "glisten", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang", "gleam_otp"], otp_app = "glisten", source = "hex", outer_checksum = "6DDE276F8A2E3C79E5A580DEA05C7D87FCDE3A37FF69F607770D92686E193531" },
12
12
+
{ name = "glisten", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib", "gleam_otp"], otp_app = "glisten", source = "hex", outer_checksum = "6DDE276F8A2E3C79E5A580DEA05C7D87FCDE3A37FF69F607770D92686E193531" },
13
13
{ name = "htmb", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../htmb" },
14
14
{ name = "ids", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_otp", "gleam_erlang"], otp_app = "ids", source = "hex", outer_checksum = "7A378014D40E848326FBEE8AC0C9B35EB9C8094DC4414D89F9A5AA99397A6042" },
15
15
-
{ name = "mist", version = "0.13.0", build_tools = ["gleam"], requirements = ["glisten", "gleam_erlang", "gleam_stdlib", "gleam_http"], otp_app = "mist", source = "hex", outer_checksum = "9A374CA245D682E2C08A5224B4420DDA252EF553AE5FD0ED7BAD33F86ACF7C98" },
15
15
+
{ name = "mist", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "gleam_erlang", "glisten"], otp_app = "mist", source = "hex", outer_checksum = "9A374CA245D682E2C08A5224B4420DDA252EF553AE5FD0ED7BAD33F86ACF7C98" },
16
16
+
{ name = "simplifile", version = "0.1.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "9CED66E65AF32C98AA336A65365A498DCF018D2A3D96A05D861C4005DCDE4D2D" },
16
17
{ name = "sqlight", version = "0.7.0", build_tools = ["gleam"], requirements = ["esqlite", "gleam_stdlib"], otp_app = "sqlight", source = "hex", outer_checksum = "BDBAA35B58E11B6DE20DC869EAA247F447268B8F398E0677F25444C9F7AE54EA" },
17
18
]
18
19
+10
action/priv/static/styles.css
···
1
1
+
* {
2
2
+
box-sizing: border-box;
3
3
+
}
4
4
+
5
5
+
body {
6
6
+
margin: 0;
7
7
+
padding: 0;
8
8
+
font-family: sans-serif;
9
9
+
background-color: hotpink;
10
10
+
}
+2
-2
action/src/action.gleam
···
8
8
9
9
pub fn main() {
10
10
let assert Ok(_) =
11
11
-
app
11
11
+
handle_request
12
12
|> framework.mist_service
13
13
|> mist.new
14
14
|> mist.port(8000)
···
17
17
process.sleep_forever()
18
18
}
19
19
20
20
-
pub fn app(request: framework.Request) {
20
20
+
pub fn handle_request(request: framework.Request) {
21
21
use db <- database.with_connection("db.sqlite3")
22
22
23
23
let context = Context(db: db)
+1
action/src/action/web.gleam
···
9
9
10
10
pub fn middleware(req: Request, service: fn(Request) -> Response) -> Response {
11
11
let req = framework.method_override(req)
12
12
+
use <- framework.serve_static(req, under: "/static", from: "priv/static")
12
13
use <- framework.log_requests(req)
13
14
use <- serve_default_responses
14
15
use <- framework.rescue_crashes
+1
framework/gleam.toml
···
13
13
gleam_stdlib = "~> 0.29"
14
14
gleam_http = "~> 3.2"
15
15
mist = "~> 0.13"
16
16
+
simplifile = "~> 0.1"
16
17
17
18
[dev-dependencies]
18
19
gleeunit = "~> 0.10"
+5
-3
framework/manifest.toml
···
4
4
packages = [
5
5
{ name = "gleam_erlang", version = "0.20.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "F216A80C8FDFF774447B494D5E08AE4E9A911E971727B9A78FEBF5F300914584" },
6
6
{ name = "gleam_http", version = "3.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "B6EB76D304E0E66267485983E6B7BC28F3BFA6795BB2BF90FC411F6903AF6A1A" },
7
7
-
{ name = "gleam_otp", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E31B158857E3D2AF946FE6E90E0CB21699AF226F4630E93FBEAC5DB4515F8920" },
7
7
+
{ name = "gleam_otp", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "E31B158857E3D2AF946FE6E90E0CB21699AF226F4630E93FBEAC5DB4515F8920" },
8
8
{ name = "gleam_stdlib", version = "0.30.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "03710B3DA047A3683117591707FCA19D32B980229DD8CE8B0603EB5B5144F6C3" },
9
9
{ name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" },
10
10
-
{ name = "glisten", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "6DDE276F8A2E3C79E5A580DEA05C7D87FCDE3A37FF69F607770D92686E193531" },
11
11
-
{ name = "mist", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_http", "glisten", "gleam_stdlib", "gleam_erlang"], otp_app = "mist", source = "hex", outer_checksum = "9A374CA245D682E2C08A5224B4420DDA252EF553AE5FD0ED7BAD33F86ACF7C98" },
10
10
+
{ name = "glisten", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_erlang", "gleam_otp"], otp_app = "glisten", source = "hex", outer_checksum = "6DDE276F8A2E3C79E5A580DEA05C7D87FCDE3A37FF69F607770D92686E193531" },
11
11
+
{ name = "mist", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "glisten", "gleam_stdlib", "gleam_http"], otp_app = "mist", source = "hex", outer_checksum = "9A374CA245D682E2C08A5224B4420DDA252EF553AE5FD0ED7BAD33F86ACF7C98" },
12
12
+
{ name = "simplifile", version = "0.1.8", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "9CED66E65AF32C98AA336A65365A498DCF018D2A3D96A05D861C4005DCDE4D2D" },
12
13
]
13
14
14
15
[requirements]
···
16
17
gleam_stdlib = { version = "~> 0.29" }
17
18
gleeunit = { version = "~> 0.10" }
18
19
mist = { version = "~> 0.13" }
20
20
+
simplifile = { version = "~> 0.1" }
+309
-20
framework/src/framework.gleam
···
8
8
// - [ ] Body writing
9
9
// - [x] Html
10
10
// - [x] Json
11
11
-
// - [ ] Static files
11
11
+
// - [x] Static files
12
12
// - [ ] Cookies
13
13
// - [ ] Signed cookies
14
14
// - [ ] Secret keys
···
18
18
// - [ ] Websockets
19
19
// - [ ] CSRF
20
20
// - [ ] Project generators
21
21
-
// - [ ] Exception recovery
21
21
+
// - [x] Exception recovery
22
22
23
23
import gleam/string_builder.{StringBuilder}
24
24
import gleam/bit_builder.{BitBuilder}
25
25
import gleam/bit_string
26
26
import gleam/erlang
27
27
+
import gleam/dynamic.{Dynamic}
27
28
import gleam/bool
28
29
import gleam/http.{Method}
29
30
import gleam/http/request.{Request as HttpRequest}
···
34
35
import gleam/uri
35
36
import gleam/io
36
37
import gleam/int
38
38
+
import simplifile
37
39
import mist
40
40
+
import mist/file as mist_file
38
41
39
42
//
40
43
// Running the server
···
88
91
let body = case response.body {
89
92
Empty -> mist.Bytes(bit_builder.new())
90
93
Text(text) -> mist.Bytes(bit_builder.from_string_builder(text))
94
94
+
File(path, content_type) -> {
95
95
+
let path = <<path:utf8>>
96
96
+
case mist_file.open(path) {
97
97
+
Error(_) -> {
98
98
+
// TODO: log error
99
99
+
mist.Bytes(bit_builder.new())
100
100
+
}
101
101
+
Ok(descriptor) -> {
102
102
+
mist.File(descriptor, content_type, 0, mist_file.size(path))
103
103
+
}
104
104
+
}
105
105
+
}
91
106
}
92
107
response
93
108
|> response.set_body(body)
···
99
114
100
115
pub type ResponseBody {
101
116
Empty
117
117
+
// TODO: remove content type
118
118
+
File(path: String, content_type: String)
102
119
Text(StringBuilder)
103
120
}
104
121
···
152
169
// TODO: document
153
170
pub fn internal_server_error() -> Response {
154
171
HttpResponse(500, [], Empty)
155
155
-
}
156
156
-
157
157
-
// TODO: test
158
158
-
// TODO: document
159
159
-
pub fn body_to_string_builder(body: ResponseBody) -> StringBuilder {
160
160
-
case body {
161
161
-
Empty -> string_builder.new()
162
162
-
Text(text) -> text
163
163
-
}
164
164
-
}
165
165
-
166
166
-
// TODO: test
167
167
-
// TODO: document
168
168
-
pub fn body_to_bit_builder(body: ResponseBody) -> BitBuilder {
169
169
-
case body {
170
170
-
Empty -> bit_builder.new()
171
171
-
Text(text) -> bit_builder.from_string_builder(text)
172
172
-
}
173
172
}
174
173
175
174
//
···
337
336
}
338
337
339
338
//
339
339
+
// MIME types
340
340
+
//
341
341
+
342
342
+
// TODO: test
343
343
+
// TODO: move to another package
344
344
+
pub fn mime_type_to_extensions(mime_type: String) -> List(String) {
345
345
+
case mime_type {
346
346
+
"application/atom+xml" -> ["atom"]
347
347
+
"application/epub+zip" -> ["epub"]
348
348
+
"application/gzip" -> ["gz"]
349
349
+
"application/java-archive" -> ["jar"]
350
350
+
"application/javascript" -> ["js"]
351
351
+
"application/json" -> ["json"]
352
352
+
"application/json-patch+json" -> ["json-patch"]
353
353
+
"application/ld+json" -> ["jsonld"]
354
354
+
"application/manifest+json" -> ["webmanifest"]
355
355
+
"application/msword" -> ["doc"]
356
356
+
"application/octet-stream" -> ["bin"]
357
357
+
"application/ogg" -> ["ogx"]
358
358
+
"application/pdf" -> ["pdf"]
359
359
+
"application/postscript" -> ["ps", "eps", "ai"]
360
360
+
"application/rss+xml" -> ["rss"]
361
361
+
"application/rtf" -> ["rtf"]
362
362
+
"application/vnd.amazon.ebook" -> ["azw"]
363
363
+
"application/vnd.api+json" -> ["json-api"]
364
364
+
"application/vnd.apple.installer+xml" -> ["mpkg"]
365
365
+
"application/vnd.etsi.asic-e+zip" -> ["asice", "sce"]
366
366
+
"application/vnd.etsi.asic-s+zip" -> ["asics", "scs"]
367
367
+
"application/vnd.mozilla.xul+xml" -> ["xul"]
368
368
+
"application/vnd.ms-excel" -> ["xls"]
369
369
+
"application/vnd.ms-fontobject" -> ["eot"]
370
370
+
"application/vnd.ms-powerpoint" -> ["ppt"]
371
371
+
"application/vnd.oasis.opendocument.presentation" -> ["odp"]
372
372
+
"application/vnd.oasis.opendocument.spreadsheet" -> ["ods"]
373
373
+
"application/vnd.oasis.opendocument.text" -> ["odt"]
374
374
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation" -> [
375
375
+
"pptx",
376
376
+
]
377
377
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" -> [
378
378
+
"xlsx",
379
379
+
]
380
380
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" -> [
381
381
+
"docx",
382
382
+
]
383
383
+
"application/vnd.rar" -> ["rar"]
384
384
+
"application/vnd.visio" -> ["vsd"]
385
385
+
"application/wasm" -> ["wasm"]
386
386
+
"application/x-7z-compressed" -> ["7z"]
387
387
+
"application/x-abiword" -> ["abw"]
388
388
+
"application/x-bzip" -> ["bz"]
389
389
+
"application/x-bzip2" -> ["bz2"]
390
390
+
"application/x-cdf" -> ["cda"]
391
391
+
"application/x-csh" -> ["csh"]
392
392
+
"application/x-freearc" -> ["arc"]
393
393
+
"application/x-httpd-php" -> ["php"]
394
394
+
"application/x-msaccess" -> ["mdb"]
395
395
+
"application/x-sh" -> ["sh"]
396
396
+
"application/x-shockwave-flash" -> ["swf"]
397
397
+
"application/x-tar" -> ["tar"]
398
398
+
"application/xhtml+xml" -> ["xhtml"]
399
399
+
"application/xml" -> ["xml"]
400
400
+
"application/zip" -> ["zip"]
401
401
+
"audio/3gpp" -> ["3gp"]
402
402
+
"audio/3gpp2" -> ["3g2"]
403
403
+
"audio/aac" -> ["aac"]
404
404
+
"audio/midi" -> ["mid", "midi"]
405
405
+
"audio/mpeg" -> ["mp3"]
406
406
+
"audio/ogg" -> ["oga"]
407
407
+
"audio/opus" -> ["opus"]
408
408
+
"audio/wav" -> ["wav"]
409
409
+
"audio/webm" -> ["weba"]
410
410
+
"font/otf" -> ["otf"]
411
411
+
"font/ttf" -> ["ttf"]
412
412
+
"font/woff" -> ["woff"]
413
413
+
"font/woff2" -> ["woff2"]
414
414
+
"image/avif" -> ["avif"]
415
415
+
"image/bmp" -> ["bmp"]
416
416
+
"image/gif" -> ["gif"]
417
417
+
"image/heic" -> ["heic"]
418
418
+
"image/heif" -> ["heif"]
419
419
+
"image/jpeg" -> ["jpg", "jpeg"]
420
420
+
"image/jxl" -> ["jxl"]
421
421
+
"image/png" -> ["png"]
422
422
+
"image/svg+xml" -> ["svg", "svgz"]
423
423
+
"image/tiff" -> ["tiff", "tif"]
424
424
+
"image/vnd.adobe.photoshop" -> ["psd"]
425
425
+
"image/vnd.microsoft.icon" -> ["ico"]
426
426
+
"image/webp" -> ["webp"]
427
427
+
"text/calendar" -> ["ics"]
428
428
+
"text/css" -> ["css"]
429
429
+
"text/csv" -> ["csv"]
430
430
+
"text/html" -> ["html", "htm"]
431
431
+
"text/javascript" -> ["js", "mjs"]
432
432
+
"text/markdown" -> ["md", "markdown"]
433
433
+
"text/plain" -> ["txt", "text"]
434
434
+
"text/xml" -> ["xml"]
435
435
+
"video/3gpp" -> ["3gp"]
436
436
+
"video/3gpp2" -> ["3g2"]
437
437
+
"video/mp2t" -> ["ts"]
438
438
+
"video/mp4" -> ["mp4"]
439
439
+
"video/mpeg" -> ["mpeg", "mpg"]
440
440
+
"video/ogg" -> ["ogv"]
441
441
+
"video/quicktime" -> ["mov"]
442
442
+
"video/webm" -> ["webm"]
443
443
+
"video/x-ms-wmv" -> ["wmv"]
444
444
+
"video/x-msvideo" -> ["avi"]
445
445
+
_ -> []
446
446
+
}
447
447
+
}
448
448
+
449
449
+
// TODO: test
450
450
+
// TODO: move to another package
451
451
+
fn extension_to_mime_type(extension: String) -> String {
452
452
+
case extension {
453
453
+
"7z" -> "application/x-7z-compressed"
454
454
+
"aac" -> "audio/aac"
455
455
+
"abw" -> "application/x-abiword"
456
456
+
"ai" -> "application/postscript"
457
457
+
"arc" -> "application/x-freearc"
458
458
+
"asice" -> "application/vnd.etsi.asic-e+zip"
459
459
+
"asics" -> "application/vnd.etsi.asic-s+zip"
460
460
+
"atom" -> "application/atom+xml"
461
461
+
"avi" -> "video/x-msvideo"
462
462
+
"avif" -> "image/avif"
463
463
+
"azw" -> "application/vnd.amazon.ebook"
464
464
+
"bin" -> "application/octet-stream"
465
465
+
"bmp" -> "image/bmp"
466
466
+
"bz" -> "application/x-bzip"
467
467
+
"bz2" -> "application/x-bzip2"
468
468
+
"cda" -> "application/x-cdf"
469
469
+
"csh" -> "application/x-csh"
470
470
+
"css" -> "text/css"
471
471
+
"csv" -> "text/csv"
472
472
+
"doc" -> "application/msword"
473
473
+
"docx" ->
474
474
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
475
475
+
"eot" -> "application/vnd.ms-fontobject"
476
476
+
"eps" -> "application/postscript"
477
477
+
"epub" -> "application/epub+zip"
478
478
+
"gif" -> "image/gif"
479
479
+
"gz" -> "application/gzip"
480
480
+
"heic" -> "image/heic"
481
481
+
"heif" -> "image/heif"
482
482
+
"htm" -> "text/html"
483
483
+
"html" -> "text/html"
484
484
+
"ico" -> "image/vnd.microsoft.icon"
485
485
+
"ics" -> "text/calendar"
486
486
+
"jar" -> "application/java-archive"
487
487
+
"jpeg" -> "image/jpeg"
488
488
+
"jpg" -> "image/jpeg"
489
489
+
"js" -> "application/javascript"
490
490
+
"json" -> "application/json"
491
491
+
"json-api" -> "application/vnd.api+json"
492
492
+
"json-patch" -> "application/json-patch+json"
493
493
+
"jsonld" -> "application/ld+json"
494
494
+
"jxl" -> "image/jxl"
495
495
+
"markdown" -> "text/markdown"
496
496
+
"md" -> "text/markdown"
497
497
+
"mdb" -> "application/x-msaccess"
498
498
+
"mid" -> "audio/midi"
499
499
+
"midi" -> "audio/midi"
500
500
+
"mjs" -> "text/javascript"
501
501
+
"mov" -> "video/quicktime"
502
502
+
"mp3" -> "audio/mpeg"
503
503
+
"mp4" -> "video/mp4"
504
504
+
"mpeg" -> "video/mpeg"
505
505
+
"mpg" -> "video/mpeg"
506
506
+
"mpkg" -> "application/vnd.apple.installer+xml"
507
507
+
"odp" -> "application/vnd.oasis.opendocument.presentation"
508
508
+
"ods" -> "application/vnd.oasis.opendocument.spreadsheet"
509
509
+
"odt" -> "application/vnd.oasis.opendocument.text"
510
510
+
"oga" -> "audio/ogg"
511
511
+
"ogv" -> "video/ogg"
512
512
+
"ogx" -> "application/ogg"
513
513
+
"opus" -> "audio/opus"
514
514
+
"otf" -> "font/otf"
515
515
+
"pdf" -> "application/pdf"
516
516
+
"php" -> "application/x-httpd-php"
517
517
+
"png" -> "image/png"
518
518
+
"ppt" -> "application/vnd.ms-powerpoint"
519
519
+
"pptx" ->
520
520
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
521
521
+
"ps" -> "application/postscript"
522
522
+
"psd" -> "image/vnd.adobe.photoshop"
523
523
+
"rar" -> "application/vnd.rar"
524
524
+
"rss" -> "application/rss+xml"
525
525
+
"rtf" -> "application/rtf"
526
526
+
"sce" -> "application/vnd.etsi.asic-e+zip"
527
527
+
"scs" -> "application/vnd.etsi.asic-s+zip"
528
528
+
"sh" -> "application/x-sh"
529
529
+
"svg" -> "image/svg+xml"
530
530
+
"svgz" -> "image/svg+xml"
531
531
+
"swf" -> "application/x-shockwave-flash"
532
532
+
"tar" -> "application/x-tar"
533
533
+
"text" -> "text/plain"
534
534
+
"tif" -> "image/tiff"
535
535
+
"tiff" -> "image/tiff"
536
536
+
"ts" -> "video/mp2t"
537
537
+
"ttf" -> "font/ttf"
538
538
+
"txt" -> "text/plain"
539
539
+
"vsd" -> "application/vnd.visio"
540
540
+
"wasm" -> "application/wasm"
541
541
+
"wav" -> "audio/wav"
542
542
+
"weba" -> "audio/webm"
543
543
+
"webm" -> "video/webm"
544
544
+
"webmanifest" -> "application/manifest+json"
545
545
+
"webp" -> "image/webp"
546
546
+
"wmv" -> "video/x-ms-wmv"
547
547
+
"woff" -> "font/woff"
548
548
+
"woff2" -> "font/woff2"
549
549
+
"xhtml" -> "application/xhtml+xml"
550
550
+
"xls" -> "application/vnd.ms-excel"
551
551
+
"xlsx" ->
552
552
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
553
553
+
"xml" -> "application/xml"
554
554
+
"xul" -> "application/vnd.mozilla.xul+xml"
555
555
+
"zip" -> "application/zip"
556
556
+
_ -> "application/octet-stream"
557
557
+
}
558
558
+
}
559
559
+
560
560
+
//
340
561
// Middleware
341
562
//
342
563
···
370
591
response
371
592
}
372
593
594
594
+
// TODO: test
595
595
+
// TODO: document
596
596
+
// TODO: remove requirement for preceeding slash on prefix
597
597
+
pub fn serve_static(
598
598
+
req: Request,
599
599
+
under prefix: String,
600
600
+
from directory: String,
601
601
+
next service: fn() -> Response,
602
602
+
) -> Response {
603
603
+
case req.method, string.starts_with(req.path, prefix) {
604
604
+
http.Get, True -> {
605
605
+
let path =
606
606
+
req.path
607
607
+
|> string.drop_left(string.length(prefix))
608
608
+
|> string.replace(each: "..", with: "")
609
609
+
|> string.replace(each: "//", with: "/")
610
610
+
|> string.append(directory, _)
611
611
+
612
612
+
let mime_type =
613
613
+
req.path
614
614
+
|> string.split(on: ".")
615
615
+
|> list.last
616
616
+
|> result.unwrap("")
617
617
+
|> extension_to_mime_type
618
618
+
619
619
+
// TODO: better check for file existence.
620
620
+
case file_info(path) {
621
621
+
Error(_) -> service()
622
622
+
Ok(_) ->
623
623
+
response.new(200)
624
624
+
|> response.set_header("content-type", mime_type)
625
625
+
|> response.set_body(File(path, mime_type))
626
626
+
}
627
627
+
}
628
628
+
_, _ -> service()
629
629
+
}
630
630
+
}
631
631
+
632
632
+
@external(erlang, "file", "read_file_info")
633
633
+
fn file_info(path: String) -> Result(Dynamic, Dynamic)
634
634
+
373
635
//
374
636
// Testing
375
637
//
376
638
377
639
// TODO: test
378
640
// TODO: document
641
641
+
// TODO: chunk the body
379
642
pub fn test_connection(body: BitString) -> Connection {
380
643
make_connection(fn(_size) {
381
644
Ok(Chunk(body, fn(_size) { Ok(ReadingFinished) }))
382
645
})
383
646
}
647
647
+
648
648
+
// TODO: test
649
649
+
// TODO: document
650
650
+
pub fn body_to_string_builder(body: ResponseBody) -> StringBuilder {
651
651
+
case body {
652
652
+
Empty -> string_builder.new()
653
653
+
Text(text) -> text
654
654
+
File(path, _) -> {
655
655
+
let assert Ok(contents) = simplifile.read(path)
656
656
+
string_builder.from_string(contents)
657
657
+
}
658
658
+
}
659
659
+
}
660
660
+
661
661
+
// TODO: test
662
662
+
// TODO: document
663
663
+
pub fn body_to_bit_builder(body: ResponseBody) -> BitBuilder {
664
664
+
case body {
665
665
+
Empty -> bit_builder.new()
666
666
+
Text(text) -> bit_builder.from_string_builder(text)
667
667
+
File(path, _) -> {
668
668
+
let assert Ok(contents) = simplifile.read_bits(path)
669
669
+
bit_builder.from_bit_string(contents)
670
670
+
}
671
671
+
}
672
672
+
}