Commit | Line | Data |
---|---|---|
49ccd711 FT |
1 | package jagi.scgi; |
2 | ||
3 | import jagi.*; | |
4 | import java.util.*; | |
5 | import java.util.function.*; | |
6 | import java.io.*; | |
7 | import java.nio.*; | |
8 | import java.nio.channels.*; | |
9 | ||
10 | public class SimpleServer implements Runnable { | |
11 | private final ServerSocketChannel sk; | |
12 | private final Function handler; | |
13 | ||
14 | public SimpleServer(ServerSocketChannel sk, Function handler) { | |
15 | this.sk = sk; | |
16 | this.handler = handler; | |
17 | } | |
18 | ||
19 | private void respond(SocketChannel cl, String status, Map resp) throws IOException { | |
20 | Object output = resp.get("jagi.output"); | |
21 | try { | |
22 | BufferedWriter fm = new BufferedWriter(Channels.newWriter(cl, Utils.UTF8.newEncoder(), -1)); | |
23 | fm.write("Status: "); | |
24 | fm.write(status); | |
25 | fm.write("\n"); | |
26 | for(Iterator it = resp.entrySet().iterator(); it.hasNext();) { | |
27 | Map.Entry ent = (Map.Entry)it.next(); | |
28 | Object val = ent.getValue(); | |
29 | if((ent.getKey() instanceof String) && (val != null)) { | |
30 | String key = (String)ent.getKey(); | |
31 | if(key.startsWith("http.")) { | |
32 | String head = key.substring(5); | |
33 | if(head.equalsIgnoreCase("status")) | |
34 | continue; | |
35 | if(val instanceof Collection) { | |
36 | for(Object part : (Collection)val) { | |
37 | fm.write(head); | |
38 | fm.write(": "); | |
39 | fm.write(part.toString()); | |
40 | fm.write("\n"); | |
41 | } | |
42 | } else { | |
43 | fm.write(head); | |
44 | fm.write(": "); | |
45 | fm.write(val.toString()); | |
46 | fm.write("\n"); | |
47 | } | |
48 | } | |
49 | } | |
50 | } | |
51 | fm.write("\n"); | |
52 | fm.flush(); | |
53 | if(output == null) { | |
54 | } else if(output instanceof byte[]) { | |
55 | Utils.writeall(cl, ByteBuffer.wrap((byte[])output)); | |
56 | } else if(output instanceof ByteBuffer) { | |
57 | Utils.writeall(cl, (ByteBuffer)output); | |
58 | } else if(output instanceof String) { | |
59 | Utils.writeall(cl, ByteBuffer.wrap(((String)output).getBytes(Utils.UTF8))); | |
60 | } else if(output instanceof CharSequence) { | |
61 | Utils.writeall(cl, Utils.UTF8.encode(CharBuffer.wrap((CharSequence)output))); | |
62 | } else if(output instanceof InputStream) { | |
63 | Utils.transfer(cl, Channels.newChannel((InputStream)output)); | |
64 | } else if(output instanceof ReadableByteChannel) { | |
65 | Utils.transfer(cl, (ReadableByteChannel)output); | |
66 | } else { | |
67 | throw(new IllegalArgumentException("response-body: " + String.valueOf(output))); | |
68 | } | |
69 | } finally { | |
70 | if(output instanceof Closeable) | |
71 | ((Closeable)output).close(); | |
72 | } | |
73 | } | |
74 | ||
75 | private void feedinput(SocketChannel cl, Map resp) throws IOException { | |
76 | Object sink = resp.get("jagi.input-sink"); | |
77 | try { | |
78 | if(sink instanceof OutputStream) { | |
79 | Utils.transfer(Channels.newChannel((OutputStream)sink), cl); | |
80 | } else if(sink instanceof WritableByteChannel) { | |
81 | Utils.transfer((WritableByteChannel)sink, cl); | |
82 | } else { | |
83 | throw(new IllegalArgumentException("input-sink: " + String.valueOf(sink))); | |
84 | } | |
85 | } finally { | |
86 | if(sink instanceof Closeable) | |
87 | ((Closeable)sink).close(); | |
88 | } | |
89 | } | |
90 | ||
91 | @SuppressWarnings("unchecked") | |
92 | private void serve(SocketChannel cl) throws IOException { | |
93 | Function handler = this.handler; | |
94 | Map<Object, Object> env = Jagi.mkenv(cl); | |
95 | Throwable error = null; | |
96 | try { | |
97 | while(true) { | |
98 | Map resp = (Map)handler.apply(env); | |
99 | String st; | |
100 | if((st = (String)resp.get("jagi.status")) != null) { | |
101 | handler = (Function)resp.get("jagi.next"); | |
102 | switch(st) { | |
103 | case "feed-input": | |
104 | feedinput(cl, resp); | |
105 | break; | |
142de7b2 FT |
106 | case "chain": |
107 | break; | |
49ccd711 FT |
108 | default: |
109 | throw(new IllegalArgumentException(st)); | |
110 | } | |
111 | } else if((st = (String)resp.get("http.status")) != null) { | |
112 | respond(cl, st, resp); | |
113 | break; | |
114 | } | |
115 | } | |
116 | } catch(Throwable t) { | |
117 | error = t; | |
118 | throw(t); | |
119 | } finally { | |
120 | Collection cleanup = (Collection)env.get("jagi.cleanup"); | |
121 | RuntimeException ce = null; | |
122 | for(Object obj : cleanup) { | |
123 | if(obj instanceof AutoCloseable) { | |
124 | try { | |
125 | ((AutoCloseable)obj).close(); | |
126 | } catch(Exception e) { | |
127 | if(error == null) | |
128 | error = ce = new RuntimeException("error(s) occurred during cleanup"); | |
129 | error.addSuppressed(e); | |
130 | } | |
131 | } | |
132 | } | |
133 | if(ce != null) | |
134 | throw(ce); | |
135 | } | |
136 | } | |
137 | ||
138 | public void run() { | |
139 | while(true) { | |
140 | SocketChannel cl; | |
141 | try { | |
142 | cl = sk.accept(); | |
143 | } catch(IOException e) { | |
144 | throw(new RuntimeException(e)); | |
145 | } | |
146 | try { | |
147 | serve(cl); | |
148 | } catch(Exception e) { | |
149 | e.printStackTrace(); | |
150 | } finally { | |
151 | try { | |
152 | cl.close(); | |
153 | } catch(IOException e) { | |
154 | e.printStackTrace(); | |
155 | } | |
156 | } | |
157 | } | |
158 | } | |
159 | } |