Added a way to properly shut down the SCGI server.
[jsvc.git] / src / dolda / jsvc / scgi / Server.java
CommitLineData
13e578b1
FT
1package dolda.jsvc.scgi;
2
3import java.util.logging.*;
4import java.io.*;
5import java.net.*;
6import java.util.*;
7
8public abstract class Server implements Runnable {
9 private final ServerSocket sk;
10 private final Logger logger = Logger.getLogger("dolda.jsvc.scgi");
0de51374 11 private boolean running = false;
13e578b1
FT
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 {
0de51374
FT
101 synchronized(this) {
102 if(running)
103 throw(new IllegalStateException("SCGI server is already running"));
104 running = true;
105 }
106 while(running) {
13e578b1
FT
107 Socket nsk = sk.accept();
108 serve(nsk);
109 }
110 } finally {
111 sk.close();
112 }
113 } catch(IOException e) {
0de51374
FT
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;
13e578b1
FT
122 }
123 }
0de51374
FT
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 }
13e578b1 136}