feat(web): file() now supports HTTP byte range
	
		
			
	
		
	
	
		
			
				
	
				ci/woodpecker/pr/docs Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/lint Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/build Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/man Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/docker Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/test Pipeline was successful
				
					Details
				
			
		
	
				
					
				
			
				
	
				ci/woodpecker/pr/docs Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/lint Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/build Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/man Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/docker Pipeline was successful
				
					Details
				
			
		
			
				
	
				ci/woodpecker/pr/test Pipeline was successful
				
					Details
				
			
		
	
							parent
							
								
									e7b45bf251
								
							
						
					
					
						commit
						cc5df95a1a
					
				|  | @ -13,7 +13,7 @@ import response { new_response } | |||
| // server is still responsive. | ||||
| ['/health'; get] | ||||
| 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 | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ pub mut: | |||
| 	logger shared log.Log | ||||
| 	// time.ticks() from start of web connection handle. | ||||
| 	// You can use it to determine how much time is spent on your request. | ||||
| 	page_gen_start    i64 | ||||
| 	page_gen_start i64 | ||||
| 	// REQUEST | ||||
| 	static_files      map[string]string | ||||
| 	static_mime_types map[string]string | ||||
|  | @ -84,8 +84,7 @@ fn (mut ctx Context) send_reader(mut reader io.Reader, size u64) ? { | |||
| 		mut to_write := bytes_read | ||||
| 
 | ||||
| 		for to_write > 0 { | ||||
| 			// TODO don't just loop infinitely here | ||||
| 			bytes_written := ctx.conn.write(buf[bytes_read - to_write..bytes_read]) or { continue } | ||||
| 			bytes_written := ctx.conn.write(buf[bytes_read - to_write..bytes_read]) or { break } | ||||
| 
 | ||||
| 			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 | ||||
| } | ||||
| 
 | ||||
| // 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` | ||||
| pub fn (mut ctx Context) json<T>(status http.Status, j T) Result { | ||||
| 	ctx.status = status | ||||
|  | @ -158,6 +148,8 @@ pub fn (mut ctx Context) file(f_path string) Result { | |||
| 		return Result{} | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.header.add(.accept_ranges, 'bytes') | ||||
| 
 | ||||
| 	file_size := os.file_size(f_path) | ||||
| 	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{} | ||||
| 	} | ||||
| 
 | ||||
| 	// We open the file before sending the headers in case reading fails | ||||
| 	mut file := os.open(f_path) or { | ||||
| 		eprintln(err.msg()) | ||||
| 		ctx.server_error(500) | ||||
| 		return Result{} | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.send_reader_response(mut file, file_size) | ||||
| 	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) | ||||
| 	} | ||||
| 
 | ||||
| 	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 | ||||
| // the given status code. | ||||
| 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 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue