Added a way to properly shut down the SCGI server.
[jsvc.git] / src / dolda / jsvc / scgi / Server.java
1 package dolda.jsvc.scgi;
2
3 import java.util.logging.*;
4 import java.io.*;
5 import java.net.*;
6 import java.util.*;
7
8 public abstract class Server implements Runnable {
9     private final ServerSocket sk;
10     private final Logger logger = Logger.getLogger("dolda.jsvc.scgi");
11     private boolean running = false;
12     public String headcs = "UTF-8";
13     
14     public Server(ServerSocket sk) {
15         this.sk = sk;
16     }
17     
18     private static int readnslen(InputStream in) throws IOException {
19         int ret = 0;
20         while(true) {
21             int c = in.read();
22             if(c == ':')
23                 return(ret);
24             else if((c >= '0') && (c <= '9'))
25                 ret = (ret * 10) + (c - '0');
26             else
27                 throw(new InvalidRequestException("Malformed netstring length"));
28         }
29     }
30     
31     private static byte[] readns(InputStream in) throws IOException {
32         byte[] buf = new byte[readnslen(in)];
33         int off = 0;
34         while(off < buf.length) {
35             int ret = in.read(buf, off, buf.length - off);
36             if(ret < 0)
37                 throw(new InvalidRequestException("Unexpected EOS in netstring"));
38             off += ret;
39         }
40         if(in.read() != ',')
41             throw(new InvalidRequestException("Unterminated netstring"));
42         return(buf);
43     }
44
45     private Map<String, String> readhead(InputStream in) throws IOException {
46         byte[] rawhead = readns(in);
47         String head = new String(rawhead, headcs);
48         Map<String, String> ret = new HashMap<String, String>();
49         int p = 0;
50         while(true) {
51             int p2 = head.indexOf(0, p);
52             if(p2 < 0) {
53                 if(p == head.length())
54                     return(ret);
55                 throw(new InvalidRequestException("Malformed headers"));
56             }
57             String key = head.substring(p, p2);
58             int p3 = head.indexOf(0, p2 + 1);
59             if(p3 < 0)
60                 throw(new InvalidRequestException("Malformed headers"));
61             String val = head.substring(p2 + 1, p3);
62             ret.put(key, val);
63             p = p3 + 1;
64         }
65     }
66
67     private boolean checkhead(Map<String, String> head) {
68         if(!head.containsKey("SCGI") || !head.get("SCGI").equals("1"))
69             return(false);
70         return(true);
71     }
72
73     protected abstract void handle(Map<String, String> head, Socket sk) throws Exception;
74
75     private void serve(Socket sk) {
76         try {
77             try {
78                 InputStream in = sk.getInputStream();
79                 Map<String, String> head = readhead(in);
80                 if(!checkhead(head))
81                     return;
82                 try {
83                     handle(head, sk);
84                 } catch(Exception e) {
85                     logger.log(Level.WARNING, "Could not handle request", e);
86                     return;
87                 }
88                 sk = null;
89             } finally {
90                 if(sk != null)
91                     sk.close();
92             }
93         } catch(IOException e) {
94             logger.log(Level.WARNING, "I/O error encountered while serving SCGI request", e);
95         }
96     }
97
98     public void run() {
99         try {
100             try {
101                 synchronized(this) {
102                     if(running)
103                         throw(new IllegalStateException("SCGI server is already running"));
104                     running = true;
105                 }
106                 while(running) {
107                     Socket nsk = sk.accept();
108                     serve(nsk);
109                 }
110             } finally {
111                 sk.close();
112             }
113         } catch(IOException e) {
114             if((e instanceof SocketException) && !running) {
115                 /* Assume that stop() has closed the socket. */
116             } else {
117                 logger.log(Level.SEVERE, "SCGI server encountered I/O error", e);
118             }
119         } finally {
120             shutdown();
121             running = false;
122         }
123     }
124     
125     public void stop() {
126         try {
127             running = false;
128             sk.close();
129         } catch(IOException e) {
130             throw(new RuntimeException(e));
131         }
132     }
133     
134     protected void shutdown() {
135     }
136 }