Skip to content

Commit

Permalink
feat: support mock response for serve
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Apr 2, 2024
1 parent 0e08836 commit dcf35cb
Show file tree
Hide file tree
Showing 21 changed files with 334 additions and 112 deletions.
9 changes: 4 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ homepage = "https://github.com/vicanso/pingap"
repository = "https://github.com/vicanso/pingap"
exclude = ["asset/*", "test/*", "Cargo.lock", "web/*", "dist/*", ".github/*"]
readme = "./README.md"
rust-version = "1.74"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ dev:
udeps:
cargo +nightly udeps

msrv:
cargo msrv verify


bloat:
cargo bloat --release --crates

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ graph TD;
locationB -- "10.0.0.2:8002" --> upstreamB2
```

## Rust version

Our current MSRV is 1.74

# License

This project is Licensed under [Apache License, Version 2.0](./LICENSE).
3 changes: 3 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@
- [ ] authentication for admin page
- [x] static file serve
- [ ] set priority for location
- [ ] mock response for upstream
- [ ] add remark for config
- [ ] support multi host for location?
8 changes: 6 additions & 2 deletions conf/pingap.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ idle_timeout = "120s"
# Anther upstream using all default config.
[upstreams.diving]
# static file
addrs = ["file://~/Downloads"]

# addrs = ["file://~/Downloads"]
addrs = [
'mock://{"status":500,"headers":["Content-Type: application/json"],"data":"{\"message\":\"Mock Service Unavailable\"}"}',
]

# Location config list, it will defined as [locations.name]
[locations.lo]
Expand All @@ -77,6 +79,8 @@ headers = ["name:value"]
# Rewrite the http url for proxy request, `^/api/ /` means replace the prefix `/api/` as `/`.
# Default `None`
rewrite = ""
# The weight of locaton, max is prior, Default `None`
# weight = 4096

# Server config list, it will be defined as [servers.name]
[servers.test]
Expand Down
33 changes: 21 additions & 12 deletions src/config/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ pub struct UpstreamConf {
}
impl UpstreamConf {
pub fn validate(&self, name: &str) -> Result<()> {
if self.addrs.is_empty() {
return Err(Error::Invalid {
message: "Upstream addrs is empty".to_string(),
});
}
// validate upstream addr
for addr in self.addrs.iter() {
let arr: Vec<_> = addr.split(' ').collect();
Expand Down Expand Up @@ -97,6 +102,7 @@ pub struct LocationConf {
pub proxy_headers: Option<Vec<String>>,
pub headers: Option<Vec<String>>,
pub rewrite: Option<String>,
pub weight: Option<u16>,
}

impl LocationConf {
Expand Down Expand Up @@ -129,27 +135,30 @@ impl LocationConf {
Ok(())
}

pub fn get_weight(&self) -> u32 {
pub fn get_weight(&self) -> u16 {
if let Some(weight) = self.weight {
return weight;
}
// path starts with
// = 65536
// prefix(default) 32768
// ~ 16384
// host exist 8192
let mut weighted: u32 = 0;
// = 1024
// prefix(default) 512
// ~ 256
// host exist 128
let mut weight: u16 = 0;
if let Some(path) = &self.path {
if path.starts_with('=') {
weighted += 65536;
weight += 1024;
} else if path.starts_with('~') {
weighted += 16384;
weight += 256;
} else {
weighted += 32768;
weight += 512;
}
weighted += path.len() as u32;
weight += path.len().min(64) as u16;
};
if self.host.is_some() {
weighted += 8192;
weight += 128;
}
weighted
weight
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/http_extra/http_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ pub static HTTP_HEADER_NO_STORE: Lazy<HttpHeader> = Lazy::new(|| {
)
});

pub static HTTP_HEADER_NO_CACHE: Lazy<HttpHeader> = Lazy::new(|| {
(
header::CACHE_CONTROL,
HeaderValue::from_str("private, no-cache").unwrap(),
)
});

pub static HTTP_HEADER_CONTENT_JSON: Lazy<HttpHeader> = Lazy::new(|| {
(
header::CONTENT_TYPE,
Expand Down
39 changes: 31 additions & 8 deletions src/http_extra/http_response.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::HTTP_HEADER_TRANSFER_CHUNKED;
use super::{HttpHeader, HTTP_HEADER_CONTENT_JSON, HTTP_HEADER_NO_STORE};
use super::{HTTP_HEADER_NO_CACHE, HTTP_HEADER_TRANSFER_CHUNKED};
use bytes::Bytes;
use http::header;
use http::StatusCode;
Expand All @@ -23,7 +23,7 @@ fn get_cache_control(max_age: Option<u32>, cache_private: Option<bool>) -> HttpH
return (header::CACHE_CONTROL, value);
}
}
HTTP_HEADER_NO_STORE.clone()
HTTP_HEADER_NO_CACHE.clone()
}

#[derive(Default, Clone)]
Expand Down Expand Up @@ -142,21 +142,24 @@ pub struct HttpChunkResponse<'r, R> {
pub headers: Option<Vec<HttpHeader>>,
}

// https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1
const DEFAULT_BUF_SIZE: usize = 8 * 1024;

impl<'r, R> HttpChunkResponse<'r, R>
where
R: tokio::io::AsyncRead + std::marker::Unpin,
{
pub fn new(r: &'r mut R) -> Self {
Self {
reader: Pin::new(r),
chunk_size: 5 * 1024,
chunk_size: DEFAULT_BUF_SIZE,
max_age: None,
headers: None,
cache_private: None,
}
}
pub async fn send(&mut self, session: &mut Session) -> pingora::Result<usize> {
let mut sent = 0;
/// Gets the response header from http chunk response
pub fn get_response_header(&self) -> pingora::Result<ResponseHeader> {
let mut resp = ResponseHeader::build(StatusCode::OK, Some(4))?;
if let Some(headers) = &self.headers {
for (name, value) in headers {
Expand All @@ -169,9 +172,13 @@ where

let cache_control = get_cache_control(self.max_age, self.cache_private);
resp.insert_header(cache_control.0, cache_control.1)?;
Ok(resp)
}
pub async fn send(&mut self, session: &mut Session) -> pingora::Result<usize> {
let header = self.get_response_header()?;
session.write_response_header(Box::new(header)).await?;

session.write_response_header(Box::new(resp)).await?;

let mut sent = 0;
let mut buffer = vec![0; self.chunk_size.max(512)];
loop {
let size = self.reader.read(&mut buffer).await.map_err(|e| {
Expand All @@ -194,12 +201,14 @@ where

#[cfg(test)]
mod tests {
use super::HttpResponse;
use super::{HttpChunkResponse, HttpResponse};
use crate::http_extra::convert_headers;
use crate::utils::resolve_path;
use bytes::Bytes;
use http::StatusCode;
use pretty_assertions::assert_eq;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::fs;
#[test]
fn test_http_response() {
let resp = HttpResponse {
Expand Down Expand Up @@ -232,4 +241,18 @@ mod tests {
format!("{header:?}")
);
}
#[tokio::test]
async fn test_http_chunk_response() {
let file = resolve_path("./error.html");
let mut f = fs::OpenOptions::new().read(true).open(file).await.unwrap();
let mut resp = HttpChunkResponse::new(&mut f);
resp.max_age = Some(3600);
resp.cache_private = Some(false);
resp.headers = Some(convert_headers(&["Contont-Type: text/html".to_string()]).unwrap());
let header = resp.get_response_header().unwrap();
assert_eq!(
r###"ResponseHeader { base: Parts { status: 200, version: HTTP/1.1, headers: {"contont-type": "text/html", "transfer-encoding": "chunked", "cache-control": "public, max-age=3600"} }, header_name_map: Some({"contont-type": CaseHeaderName(b"contont-type"), "transfer-encoding": CaseHeaderName(b"Transfer-Encoding"), "cache-control": CaseHeaderName(b"Cache-Control")}) }"###,
format!("{header:?}")
);
}
}
3 changes: 3 additions & 0 deletions src/proxy/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ mod tests {
Upstream::new(
upstream_name,
&UpstreamConf {
addrs: vec!["127.0.0.1".to_string()],
..Default::default()
},
)
Expand Down Expand Up @@ -298,6 +299,7 @@ mod tests {
Upstream::new(
upstream_name,
&UpstreamConf {
addrs: vec!["127.0.0.1".to_string()],
..Default::default()
},
)
Expand Down Expand Up @@ -325,6 +327,7 @@ mod tests {
Upstream::new(
upstream_name,
&UpstreamConf {
addrs: vec!["127.0.0.1".to_string()],
..Default::default()
},
)
Expand Down
3 changes: 2 additions & 1 deletion src/proxy/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ fn format_extra_tag(key: &str) -> Option<Tag> {
}

static COMBINED: &str =
r###"{remote} "{method} {uri} {proto}" {status} {size-human} "{referer}" "{userAgent}""###;
r###"{remote} "{method} {uri} {proto}" {status} {size-human} "{referer}" "{user-agent}""###;
static COMMON: &str = r###"{remote} "{method} {uri} {proto}" {status} {size-human}""###;
static SHORT: &str = r###"{remote} {method} {uri} {proto} {status} {size-human} - {latency}ms"###;
static TINY: &str = r###"{method} {uri} {status} {size-human} - {latency}ms"###;
Expand Down Expand Up @@ -280,6 +280,7 @@ impl Parser {
} else if let Some(value) = get_req_header_value(req_header, "X-Real-Ip") {
buf.push_str(value);
}
// TODO remote addr
}
TagCategory::Scheme => {
// TODO
Expand Down
4 changes: 4 additions & 0 deletions src/proxy/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ impl ProxyHttp for Server {
let result = dir.handle(session, ctx).await?;
return Ok(result);
}
if let Some(mock) = lo.upstream.get_mock() {
let result = mock.handle(session, ctx).await?;
return Ok(result);
}
// TODO get response from cache
// check location support cache

Expand Down
Loading

0 comments on commit dcf35cb

Please sign in to comment.