Initial commit.
[jagi.git] / src / jagi / scgi / SimpleServer.java
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;
106                     default:
107                         throw(new IllegalArgumentException(st));
108                     }
109                 } else if((st = (String)resp.get("http.status")) != null) {
110                     respond(cl, st, resp);
111                     break;
112                 }
113             }
114         } catch(Throwable t) {
115             error = t;
116             throw(t);
117         } finally {
118             Collection cleanup = (Collection)env.get("jagi.cleanup");
119             RuntimeException ce = null;
120             for(Object obj : cleanup) {
121                 if(obj instanceof AutoCloseable) {
122                     try {
123                         ((AutoCloseable)obj).close();
124                     } catch(Exception e) {
125                         if(error == null)
126                             error = ce = new RuntimeException("error(s) occurred during cleanup");
127                         error.addSuppressed(e);
128                     }
129                 }
130             }
131             if(ce != null)
132                 throw(ce);
133         }
134     }
135
136     public void run() {
137         while(true) {
138             SocketChannel cl;
139             try {
140                 cl = sk.accept();
141             } catch(IOException e) {
142                 throw(new RuntimeException(e));
143             }
144             try {
145                 serve(cl);
146             } catch(Exception e) {
147                 e.printStackTrace();
148             } finally {
149                 try {
150                     cl.close();
151                 } catch(IOException e) {
152                     e.printStackTrace();
153                 }
154             }
155         }
156     }
157 }