feat(web): file() now supports HTTP byte range

Jef Roosens 2022-08-12 17:11:44 +02:00
parent e7b45bf251
commit cc5df95a1a
Signed by untrusted user: Jef Roosens
GPG Key ID: B75D4F293C7052DB
2 changed files with 50 additions and 16 deletions

View File

@ -13,7 +13,7 @@ import response { new_response }
// server is still responsive. // server is still responsive.
['/health'; get] ['/health'; get]
pub fn (mut app App) healthcheck() web.Result { pub fn (mut app App) healthcheck() web.Result {
return app.json(http.Status.ok, new_response('Healthy.')) return app.json(.ok, new_response('Healthy.'))
} }
// get_repo_file handles all Pacman-related routes. It returns both the // get_repo_file handles all Pacman-related routes. It returns both the

View File

@ -84,8 +84,7 @@ fn (mut ctx Context) send_reader(mut reader io.Reader, size u64) ? {
mut to_write := bytes_read mut to_write := bytes_read
for to_write > 0 { for to_write > 0 {
// TODO don't just loop infinitely here bytes_written := ctx.conn.write(buf[bytes_read - to_write..bytes_read]) or { break }
bytes_written := ctx.conn.write(buf[bytes_read - to_write..bytes_read]) or { continue }
to_write = to_write - bytes_written to_write = to_write - bytes_written
} }
@ -127,15 +126,6 @@ pub fn (mut ctx Context) send_reader_response(mut reader io.Reader, size u64) bo
return true return true
} }
// text responds to a request with some plaintext.
pub fn (mut ctx Context) text(status http.Status, s string) Result {
ctx.status = status
ctx.content_type = 'text/plain'
ctx.send_response(s)
return Result{}
}
// json<T> HTTP_OK with json_s as payload with content-type `application/json` // json<T> HTTP_OK with json_s as payload with content-type `application/json`
pub fn (mut ctx Context) json<T>(status http.Status, j T) Result { pub fn (mut ctx Context) json<T>(status http.Status, j T) Result {
ctx.status = status ctx.status = status
@ -158,6 +148,8 @@ pub fn (mut ctx Context) file(f_path string) Result {
return Result{} return Result{}
} }
ctx.header.add(.accept_ranges, 'bytes')
file_size := os.file_size(f_path) file_size := os.file_size(f_path)
ctx.header.add(http.CommonHeader.content_length, file_size.str()) ctx.header.add(http.CommonHeader.content_length, file_size.str())
@ -168,14 +160,53 @@ pub fn (mut ctx Context) file(f_path string) Result {
return Result{} return Result{}
} }
// We open the file before sending the headers in case reading fails
mut file := os.open(f_path) or { mut file := os.open(f_path) or {
eprintln(err.msg()) eprintln(err.msg())
ctx.server_error(500) ctx.server_error(500)
return Result{} return Result{}
} }
defer {
file.close()
}
if range_str := ctx.req.header.get(.range) {
mut parts := range_str.split_nth('=', 2)
if parts[0] != 'bytes' {
ctx.status = .requested_range_not_satisfiable
ctx.header.delete(.content_length)
ctx.send()
return Result{}
}
parts = parts[1].split_nth('-', 2)
start := parts[0].i64()
end := if parts[1] == '' { file_size - 1 } else { parts[1].u64() }
// Either the actual number 0 or the result of an invalid integer
if end == 0 {
ctx.status = .requested_range_not_satisfiable
ctx.header.delete(.content_length)
ctx.send()
return Result{}
}
// Move cursor to start of data to read
file.seek(start, .start) or {
ctx.server_error(500)
return Result{}
}
length := end - u64(start) + 1
ctx.status = .partial_content
ctx.header.set(.content_length, length.str())
ctx.send_reader_response(mut file, length)
} else {
ctx.send_reader_response(mut file, file_size) ctx.send_reader_response(mut file, file_size)
}
return Result{} return Result{}
} }
@ -183,7 +214,10 @@ pub fn (mut ctx Context) file(f_path string) Result {
// status responds with an empty textual response, essentially only returning // status responds with an empty textual response, essentially only returning
// the given status code. // the given status code.
pub fn (mut ctx Context) status(status http.Status) Result { pub fn (mut ctx Context) status(status http.Status) Result {
return ctx.text(status, '') ctx.status = status
ctx.send()
return Result{}
} }
// server_error Response a server error // server_error Response a server error