Skip to content

Commit 8e3af59

Browse files
committed
provide basic hashmap API for HTTP responding; bump 0.0.4
1 parent dc60909 commit 8e3af59

File tree

6 files changed

+219
-51
lines changed

6 files changed

+219
-51
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "calcit_http"
3-
version = "0.0.3"
3+
version = "0.0.4"
44
authors = ["jiyinyiyong <[email protected]>"]
55
edition = "2018"
66

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@
77
APIs:
88

99
```cirru
10-
http.core/serve-http? $ {}
11-
:port 4000
10+
http.core/serve-http!
11+
{}
12+
:port 4000
13+
:host "|0.0.0.0"
14+
fn (req)
15+
on-request req
16+
17+
defn on-request (req)
18+
{}
19+
:code 200
20+
:headers $ {}
21+
:content-type |application/json
22+
:body "|some content"
1223
```
1324

1425
Install to `~/.config/calcit/modules/`, compile and provide `*.{dylib,so}` file with `./build.sh`.

calcit.cirru

Lines changed: 70 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

compact.cirru

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{} (:package |http)
33
:configs $ {} (:init-fn |http.test/main!) (:reload-fn |http.test/reload!)
44
:modules $ []
5-
:version |0.0.3
5+
:version |0.0.4
66
:files $ {}
77
|http.core $ {}
88
:ns $ quote
@@ -19,19 +19,25 @@
1919
http.core :refer $ serve-http!
2020
http.$meta :refer $ calcit-dirname calcit-filename
2121
:defs $ {}
22+
|mid-call $ quote
23+
defn mid-call () $ println "\"Calling internal function"
24+
|on-request $ quote
25+
defn on-request (req) (; println "\"Handling request:" req)
26+
println $ :url req
27+
; mid-call
28+
{} (:status :ok) (:code 200)
29+
:headers $ {} (:content-type "\"application/json")
30+
:body $ format-cirru-edn req
2231
|run-tests $ quote
2332
defn run-tests () (println "\"%%%% test for lib") (println calcit-filename calcit-dirname) (println "\"No tests...")
2433
|main! $ quote
2534
defn main! () $ run-tests
26-
|mid-f $ quote
27-
defn mid-f () $ println "\"fff222"
2835
|demo-server! $ quote
2936
defn demo-server! () $ serve-http!
3037
{} $ :port 4000
31-
fn (req) (println "\"got request2" req) (mid-f)
32-
{} (:status :ok) (:code 200) (:body "\"TODO some Body")
38+
fn (req) (on-request req)
3339
|reload! $ quote
34-
defn reload! $
40+
defn reload! () $ println "\"Reload"
3541
|http.util $ {}
3642
:ns $ quote
3743
ns http.util $ :require

src/lib.rs

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
use cirru_edn::Edn;
22
use std::collections::HashMap;
33
use std::sync::Arc;
4-
use tiny_http::{Response, Server};
4+
use tiny_http::{Method, Response, Server};
5+
6+
struct HttpServerOptions {
7+
port: u16,
8+
host: String,
9+
}
10+
11+
struct ResponseSkeleton {
12+
code: u8,
13+
headers: HashMap<String, String>,
14+
body: String,
15+
}
516

617
#[no_mangle]
718
pub fn serve_http(
819
args: Vec<Edn>,
9-
handler: Arc<dyn Fn(Edn) -> Result<Edn, String>>,
20+
handler: Arc<dyn Fn(Vec<Edn>) -> Result<Edn, String>>,
1021
) -> Result<Edn, String> {
11-
println!("TODO args: {:?}", args);
12-
let server = Server::http("0.0.0.0:8000").unwrap();
22+
if args.is_empty() {
23+
return Err(format!("expected an option, got nothing: {:?}", args));
24+
}
25+
let options = parse_options(&args[0])?;
26+
let server = Server::http(&format!("{}:{}", options.host, options.port)).unwrap();
27+
println!("Server started at {}:{}", options.host, options.port);
1328

14-
for request in server.incoming_requests() {
29+
for mut request in server.incoming_requests() {
1530
// println!(
1631
// "received request! method: {:?}, url: {:?}, headers: {:?}",
1732
// request.method(),
@@ -22,18 +37,117 @@ pub fn serve_http(
2237
let mut m: HashMap<Edn, Edn> = HashMap::new();
2338
m.insert(
2439
Edn::Keyword(String::from("method")),
25-
Edn::Str(request.method().to_string()),
40+
Edn::Keyword(request.method().to_string()),
2641
);
2742
m.insert(
2843
Edn::Keyword(String::from("url")),
2944
Edn::Str(request.url().to_string()),
3045
);
46+
47+
let mut headers: HashMap<Edn, Edn> = HashMap::new();
48+
49+
for pair in request.headers() {
50+
headers.insert(
51+
Edn::Keyword(pair.field.to_string()),
52+
Edn::Str(pair.value.to_string()),
53+
);
54+
}
55+
m.insert(Edn::Keyword(String::from("headers")), Edn::Map(headers));
56+
57+
if request.method() != &Method::Get {
58+
let mut content = String::new();
59+
request.as_reader().read_to_string(&mut content).unwrap();
60+
m.insert(
61+
Edn::Keyword(String::from("body")),
62+
Edn::Str(content.to_string()),
63+
);
64+
}
65+
3166
let info = Edn::Map(m);
32-
let result = handler(info)?;
67+
let result = handler(vec![info])?;
68+
let res = parse_response(&result)?;
69+
70+
let mut response = Response::from_string(res.body.to_string()).with_status_code(res.code);
3371

34-
let response = Response::from_string(result.to_string());
72+
for (field, value) in res.headers {
73+
response.add_header(
74+
format!("{}: {}", field, value)
75+
.parse::<tiny_http::Header>()
76+
.unwrap(),
77+
);
78+
}
3579
request.respond(response).map_err(|x| x.to_string())?;
3680
}
3781

3882
Ok(Edn::Nil)
3983
}
84+
85+
fn parse_options(d: &Edn) -> Result<HttpServerOptions, String> {
86+
match d {
87+
Edn::Nil => Ok(HttpServerOptions {
88+
port: 4000,
89+
host: String::from("0.0.0.0"),
90+
}),
91+
Edn::Map(m) => {
92+
let mut options = HttpServerOptions {
93+
port: 4000,
94+
host: String::from("0.0.0.0"),
95+
};
96+
options.port = match m.get(&Edn::Keyword(String::from("port"))) {
97+
Some(Edn::Number(port)) => *port as u16,
98+
None => 4000,
99+
a => return Err(format!("invalid config for port: {:?}", a)),
100+
};
101+
options.host = match m.get(&Edn::Keyword(String::from("host"))) {
102+
Some(Edn::Str(host)) => host.to_owned(),
103+
None => String::from("0.0.0.0"),
104+
a => return Err(format!("invalid config for host: {:?}", a)),
105+
};
106+
Ok(options)
107+
}
108+
_ => Err(format!("invalid data for options: {}", d)),
109+
}
110+
}
111+
112+
/// from user response
113+
fn parse_response(info: &Edn) -> Result<ResponseSkeleton, String> {
114+
if let Edn::Map(m) = info {
115+
let mut res = ResponseSkeleton {
116+
code: 200,
117+
headers: HashMap::new(),
118+
body: String::from(""),
119+
};
120+
res.code = match m.get(&Edn::Keyword(String::from("code"))) {
121+
Some(Edn::Number(n)) => *n as u8,
122+
None => 200,
123+
a => return Err(format!("invalid code: {:?}", a)),
124+
};
125+
res.body = match m.get(&Edn::Keyword(String::from("body"))) {
126+
Some(Edn::Str(s)) => s.to_owned(),
127+
Some(a) => a.to_string(),
128+
None => String::from(""),
129+
};
130+
res.headers = match m.get(&Edn::Keyword(String::from("headers"))) {
131+
Some(Edn::Map(m)) => {
132+
let mut hs: HashMap<String, String> = HashMap::new();
133+
for (k, v) in m {
134+
if let Edn::Keyword(s) = k {
135+
if let Edn::Str(s2) = v {
136+
hs.insert(s.to_owned(), s2.to_owned());
137+
} else {
138+
hs.insert(s.to_owned(), v.to_string());
139+
}
140+
} else {
141+
return Err(format!("invalid head entry: {}", k));
142+
}
143+
}
144+
hs
145+
}
146+
Some(a) => return Err(format!("invalid data for headers: {}", a)),
147+
None => HashMap::new(),
148+
};
149+
Ok(res)
150+
} else {
151+
Err(format!("invalid response shape: {}", info))
152+
}
153+
}

0 commit comments

Comments
 (0)