blob: f3355fca505dd8af3825995c294d9b7eda1e4333 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Server Response Writer module for micro HTTP server.
//!
//! This module implements a basic response writer that can pass
//! chunked http responses between a uri handler and the network.
//!
//! The main use is for streaming large files from the capture_handler()
//! to the Http client.
//!
//! This library is intended solely for serving netsim clients.
use std::io::Write;
use crate::http_server::http_response::HttpResponse;
use super::http_request::StrHeaders;
pub type ResponseWritable<'a> = &'a mut dyn ServerResponseWritable;
// ServerResponseWritable trait is used by both the Http and gRPC
// servers.
pub trait ServerResponseWritable {
fn put_ok_with_length(&mut self, mime_type: &str, length: usize, headers: StrHeaders);
fn put_chunk(&mut self, chunk: &[u8]);
fn put_ok(&mut self, mime_type: &str, body: &str, headers: StrHeaders);
fn put_error(&mut self, error_code: u16, error_message: &str);
fn put_ok_with_vec(&mut self, mime_type: &str, body: Vec<u8>, headers: StrHeaders);
}
// A response writer that can contain a TCP stream or other writable.
pub struct ServerResponseWriter<'a> {
writer: &'a mut dyn Write,
}
impl<'a> ServerResponseWriter<'a> {
pub fn new<W: Write>(writer: &mut W) -> ServerResponseWriter {
ServerResponseWriter { writer }
}
pub fn put_response(&mut self, response: HttpResponse) {
let mut buffer = format!("HTTP/1.1 {}\r\n", response.status_code).into_bytes();
for (name, value) in response.headers.iter() {
buffer.extend_from_slice(format!("{name}: {value}\r\n").as_bytes());
}
buffer.extend_from_slice(b"\r\n");
buffer.extend_from_slice(&response.body);
if let Err(e) = self.writer.write_all(&buffer) {
println!("netsim: handle_connection error {e}");
};
}
}
// Implement the ServerResponseWritable trait for the
// ServerResponseWriter struct. These methods are called
// by the handler methods.
impl ServerResponseWritable for ServerResponseWriter<'_> {
fn put_error(&mut self, error_code: u16, error_message: &str) {
let response = HttpResponse::new_error(error_code, error_message.into());
self.put_response(response);
}
fn put_chunk(&mut self, chunk: &[u8]) {
if let Err(e) = self.writer.write_all(chunk) {
println!("netsim: handle_connection error {e}");
};
self.writer.flush().unwrap();
}
fn put_ok_with_length(&mut self, mime_type: &str, length: usize, headers: StrHeaders) {
let mut response = HttpResponse::new_ok_with_length(mime_type, length);
response.add_headers(headers);
self.put_response(response);
}
fn put_ok(&mut self, mime_type: &str, body: &str, headers: StrHeaders) {
let mut response = HttpResponse::new_ok(mime_type, body.into());
response.add_headers(headers);
self.put_response(response);
}
fn put_ok_with_vec(&mut self, mime_type: &str, body: Vec<u8>, headers: StrHeaders) {
let mut response = HttpResponse::new_ok(mime_type, body);
response.add_headers(headers);
self.put_response(response);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_put_error() {
let mut stream = Cursor::new(Vec::new());
let mut writer = ServerResponseWriter::new(&mut stream);
writer.put_error(404, "Hello World");
let written_bytes = stream.get_ref();
let expected_bytes =
b"HTTP/1.1 404\r\nContent-Type: text/plain\r\nContent-Length: 11\r\n\r\nHello World";
assert_eq!(written_bytes, expected_bytes);
}
#[test]
fn test_put_ok() {
let mut stream = Cursor::new(Vec::new());
let mut writer = ServerResponseWriter::new(&mut stream);
writer.put_ok("text/plain", "Hello World", &[]);
let written_bytes = stream.get_ref();
let expected_bytes =
b"HTTP/1.1 200\r\nContent-Type: text/plain\r\nContent-Length: 11\r\n\r\nHello World";
assert_eq!(written_bytes, expected_bytes);
}
}