Allow jagidir $use directives to specify absolute paths.
[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                     case "chain":
107                         break;
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 }