Skip to content

Commit 0ecf3be

Browse files
authored
Merge pull request #2 from calcit-lang/api-options
provide basic hashmap API for HTTP responding
2 parents dc60909 + 129207d commit 0ecf3be

File tree

6 files changed

+225
-51
lines changed

6 files changed

+225
-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.5"
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.5
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: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
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+
}
16+
17+
#[no_mangle]
18+
pub fn abi_version() -> String {
19+
String::from("0.0.1")
20+
}
521

622
#[no_mangle]
723
pub fn serve_http(
824
args: Vec<Edn>,
9-
handler: Arc<dyn Fn(Edn) -> Result<Edn, String>>,
25+
handler: Arc<dyn Fn(Vec<Edn>) -> Result<Edn, String>>,
26+
_finish: Box<dyn FnOnce()>,
1027
) -> Result<Edn, String> {
11-
println!("TODO args: {:?}", args);
12-
let server = Server::http("0.0.0.0:8000").unwrap();
28+
if args.is_empty() {
29+
return Err(format!("expected an option, got nothing: {:?}", args));
30+
}
31+
let options = parse_options(&args[0])?;
32+
let server = Server::http(&format!("{}:{}", options.host, options.port)).unwrap();
33+
println!("Server started at {}:{}", options.host, options.port);
1334

14-
for request in server.incoming_requests() {
35+
for mut request in server.incoming_requests() {
1536
// println!(
1637
// "received request! method: {:?}, url: {:?}, headers: {:?}",
1738
// request.method(),
@@ -22,18 +43,117 @@ pub fn serve_http(
2243
let mut m: HashMap<Edn, Edn> = HashMap::new();
2344
m.insert(
2445
Edn::Keyword(String::from("method")),
25-
Edn::Str(request.method().to_string()),
46+
Edn::Keyword(request.method().to_string()),
2647
);
2748
m.insert(
2849
Edn::Keyword(String::from("url")),
2950
Edn::Str(request.url().to_string()),
3051
);
52+
53+
let mut headers: HashMap<Edn, Edn> = HashMap::new();
54+
55+
for pair in request.headers() {
56+
headers.insert(
57+
Edn::Keyword(pair.field.to_string()),
58+
Edn::Str(pair.value.to_string()),
59+
);
60+
}
61+
m.insert(Edn::Keyword(String::from("headers")), Edn::Map(headers));
62+
63+
if request.method() != &Method::Get {
64+
let mut content = String::new();
65+
request.as_reader().read_to_string(&mut content).unwrap();
66+
m.insert(
67+
Edn::Keyword(String::from("body")),
68+
Edn::Str(content.to_string()),
69+
);
70+
}
71+
3172
let info = Edn::Map(m);
32-
let result = handler(info)?;
73+
let result = handler(vec![info])?;
74+
let res = parse_response(&result)?;
75+
76+
let mut response = Response::from_string(res.body.to_string()).with_status_code(res.code);
3377

34-
let response = Response::from_string(result.to_string());
78+
for (field, value) in res.headers {
79+
response.add_header(
80+
format!("{}: {}", field, value)
81+
.parse::<tiny_http::Header>()
82+
.unwrap(),
83+
);
84+
}
3585
request.respond(response).map_err(|x| x.to_string())?;
3686
}
3787

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

0 commit comments

Comments
 (0)