1
1
use cirru_edn:: Edn ;
2
2
use std:: collections:: HashMap ;
3
3
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
+ }
5
16
6
17
#[ no_mangle]
7
18
pub fn serve_http (
8
19
args : Vec < Edn > ,
9
- handler : Arc < dyn Fn ( Edn ) -> Result < Edn , String > > ,
20
+ handler : Arc < dyn Fn ( Vec < Edn > ) -> Result < Edn , String > > ,
10
21
) -> 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) ;
13
28
14
- for request in server. incoming_requests ( ) {
29
+ for mut request in server. incoming_requests ( ) {
15
30
// println!(
16
31
// "received request! method: {:?}, url: {:?}, headers: {:?}",
17
32
// request.method(),
@@ -22,18 +37,117 @@ pub fn serve_http(
22
37
let mut m: HashMap < Edn , Edn > = HashMap :: new ( ) ;
23
38
m. insert (
24
39
Edn :: Keyword ( String :: from ( "method" ) ) ,
25
- Edn :: Str ( request. method ( ) . to_string ( ) ) ,
40
+ Edn :: Keyword ( request. method ( ) . to_string ( ) ) ,
26
41
) ;
27
42
m. insert (
28
43
Edn :: Keyword ( String :: from ( "url" ) ) ,
29
44
Edn :: Str ( request. url ( ) . to_string ( ) ) ,
30
45
) ;
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
+
31
66
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 ) ;
33
71
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
+ }
35
79
request. respond ( response) . map_err ( |x| x. to_string ( ) ) ?;
36
80
}
37
81
38
82
Ok ( Edn :: Nil )
39
83
}
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