リビジョン | 35 (tree) |
---|---|
日時 | 2013-09-28 07:29:42 |
作者 | y-moriguchi |
tiny HTTP server included
@@ -0,0 +1,45 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | +import java.io.PrintWriter; | |
20 | +import java.nio.charset.Charset; | |
21 | + | |
22 | +/** | |
23 | + * | |
24 | + * | |
25 | + * | |
26 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
27 | + */ | |
28 | +public interface HTTPDispatch { | |
29 | + | |
30 | + /** | |
31 | + * | |
32 | + * @return | |
33 | + */ | |
34 | + public Charset getCharset(); | |
35 | + | |
36 | + /** | |
37 | + * | |
38 | + * @param r | |
39 | + * @param req | |
40 | + * @throws IOException | |
41 | + */ | |
42 | + public void execute(HTTPRequest r, | |
43 | + PrintWriter w) throws IOException; | |
44 | + | |
45 | +} |
@@ -0,0 +1,109 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.util.logging.Level; | |
19 | + | |
20 | +/** | |
21 | + * | |
22 | + * | |
23 | + * | |
24 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
25 | + */ | |
26 | +public abstract class HTTPLogger { | |
27 | + | |
28 | + protected Level level = Level.ALL; | |
29 | + | |
30 | + public Level getLevel() { | |
31 | + return level; | |
32 | + } | |
33 | + | |
34 | + public void setLevel(Level level) { | |
35 | + this.level = level; | |
36 | + } | |
37 | + | |
38 | + /** | |
39 | + * | |
40 | + * @param level | |
41 | + * @param format | |
42 | + * @param o | |
43 | + */ | |
44 | + public abstract void log(Level level, String format, Object... o); | |
45 | + | |
46 | + /** | |
47 | + * | |
48 | + * @param format | |
49 | + * @param os | |
50 | + */ | |
51 | + public void severe(String format, Object... os) { | |
52 | + log(Level.SEVERE, format, os); | |
53 | + } | |
54 | + | |
55 | + /** | |
56 | + * | |
57 | + * @param format | |
58 | + * @param os | |
59 | + */ | |
60 | + public void warning(String format, Object... os) { | |
61 | + log(Level.WARNING, format, os); | |
62 | + } | |
63 | + | |
64 | + /** | |
65 | + * | |
66 | + * @param format | |
67 | + * @param os | |
68 | + */ | |
69 | + public void info(String format, Object... os) { | |
70 | + log(Level.INFO, format, os); | |
71 | + } | |
72 | + | |
73 | + /** | |
74 | + * | |
75 | + * @param format | |
76 | + * @param os | |
77 | + */ | |
78 | + public void config(String format, Object... os) { | |
79 | + log(Level.CONFIG, format, os); | |
80 | + } | |
81 | + | |
82 | + /** | |
83 | + * | |
84 | + * @param format | |
85 | + * @param os | |
86 | + */ | |
87 | + public void fine(String format, Object... os) { | |
88 | + log(Level.FINE, format, os); | |
89 | + } | |
90 | + | |
91 | + /** | |
92 | + * | |
93 | + * @param format | |
94 | + * @param os | |
95 | + */ | |
96 | + public void finer(String format, Object... os) { | |
97 | + log(Level.FINER, format, os); | |
98 | + } | |
99 | + | |
100 | + /** | |
101 | + * | |
102 | + * @param format | |
103 | + * @param os | |
104 | + */ | |
105 | + public void finest(String format, Object... os) { | |
106 | + log(Level.FINEST, format, os); | |
107 | + } | |
108 | + | |
109 | +} |
@@ -0,0 +1,109 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | +import java.io.PrintWriter; | |
20 | +import java.nio.ByteBuffer; | |
21 | +import java.nio.channels.SocketChannel; | |
22 | + | |
23 | +/** | |
24 | + * | |
25 | + * | |
26 | + * | |
27 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
28 | + */ | |
29 | +public final class HTTPUtils { | |
30 | + | |
31 | + private static final String MSG200 = | |
32 | + "HTTP/1.0 200 OK\r\n"; | |
33 | + private static final String MSG400 = | |
34 | + "HTTP/1.0 400 Bad Request\r\n"; | |
35 | + private static final String MSG404 = | |
36 | + "HTTP/1.0 404 Not Found\r\n"; | |
37 | + private static final String MSG500 = | |
38 | + "HTTP/1.0 500 Internal Error\r\n"; | |
39 | + | |
40 | + static final Ecrire MESSAGE400 = new Ecrire() { | |
41 | + | |
42 | + @Override | |
43 | + public void write(SocketChannel c) throws IOException { | |
44 | + HTTPUtils.putBadRequest(c); | |
45 | + c.close(); | |
46 | + } | |
47 | + | |
48 | + }; | |
49 | + | |
50 | + static final Ecrire MESSAGE404 = new Ecrire() { | |
51 | + | |
52 | + @Override | |
53 | + public void write(SocketChannel c) throws IOException { | |
54 | + HTTPUtils.putNotFound(c); | |
55 | + c.close(); | |
56 | + } | |
57 | + | |
58 | + }; | |
59 | + | |
60 | + static final Ecrire MESSAGE500 = new Ecrire() { | |
61 | + | |
62 | + @Override | |
63 | + public void write(SocketChannel c) throws IOException { | |
64 | + HTTPUtils.putBadRequest(c); | |
65 | + c.close(); | |
66 | + } | |
67 | + | |
68 | + }; | |
69 | + | |
70 | + private HTTPUtils() {} | |
71 | + | |
72 | + public static void puterror(SocketChannel c, | |
73 | + Throwable e) throws IOException { | |
74 | + c.write(ByteBuffer.wrap(MSG500.getBytes())); | |
75 | + } | |
76 | + | |
77 | + public static void putApplicationError(PrintWriter w, | |
78 | + Throwable e) throws IOException { | |
79 | + w.println("<html>"); | |
80 | + w.println("<head>"); | |
81 | + w.println("<title>500 Internal Error</title>"); | |
82 | + w.println("</head>"); | |
83 | + w.println("<body>"); | |
84 | + w.println("<h1>500 Internal Error</h1>"); | |
85 | + w.println("<pre>"); | |
86 | + e.printStackTrace(w); | |
87 | + w.println("</pre>"); | |
88 | + w.println("</body>"); | |
89 | + w.println("</html>"); | |
90 | + } | |
91 | + | |
92 | + public static void putOk(SocketChannel c) throws IOException { | |
93 | + c.write(ByteBuffer.wrap(MSG200.getBytes())); | |
94 | + } | |
95 | + | |
96 | + public static void putBadRequest(SocketChannel c) throws IOException { | |
97 | + c.write(ByteBuffer.wrap(MSG400.getBytes())); | |
98 | + } | |
99 | + | |
100 | + public static void putNotFound(SocketChannel c) throws IOException { | |
101 | + c.write(ByteBuffer.wrap(MSG404.getBytes())); | |
102 | + } | |
103 | + | |
104 | + public static void put(SocketChannel c, | |
105 | + String s) throws IOException { | |
106 | + c.write(ByteBuffer.wrap(s.getBytes())); | |
107 | + } | |
108 | + | |
109 | +} |
@@ -0,0 +1,93 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | +import java.io.InputStream; | |
20 | + | |
21 | +/** | |
22 | + * | |
23 | + * | |
24 | + * | |
25 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
26 | + */ | |
27 | +public class URLEncodedInputStream extends InputStream { | |
28 | + | |
29 | + private static enum S { INI, P01, P02 } | |
30 | + | |
31 | + private S etat = S.INI; | |
32 | + private InputStream ins; | |
33 | + | |
34 | + /** | |
35 | + * | |
36 | + * @param ins | |
37 | + */ | |
38 | + public URLEncodedInputStream() { | |
39 | + this.ins = null; | |
40 | + } | |
41 | + | |
42 | + /** | |
43 | + * | |
44 | + * @param ins | |
45 | + * @throws IOException | |
46 | + */ | |
47 | + public void newInputStream(InputStream ins) throws IOException { | |
48 | + if(this.ins != null) this.ins.close(); | |
49 | + this.ins = ins; | |
50 | + } | |
51 | + | |
52 | + @Override | |
53 | + public int read() throws IOException { | |
54 | + int c, r = 0; | |
55 | + | |
56 | + if(ins == null) throw new IllegalStateException(); | |
57 | + while(true) { | |
58 | + c = ins.read(); | |
59 | + switch(etat) { | |
60 | + case INI: | |
61 | + if(c < 0) { | |
62 | + return -1; | |
63 | + } else if(c == '+') { | |
64 | + return ' '; | |
65 | + } else if(c == '%') { | |
66 | + etat = S.P01; r = 0; | |
67 | + break; | |
68 | + } else { | |
69 | + return c; | |
70 | + } | |
71 | + case P01: | |
72 | + if(c >= '0' && c <= '9') { | |
73 | + etat = S.P02; r = c - '0'; | |
74 | + break; | |
75 | + } else { | |
76 | + return '%'; | |
77 | + } | |
78 | + case P02: | |
79 | + if(c >= '0' && c <= '9') { | |
80 | + return r * 10 + (c - '0'); | |
81 | + } else { | |
82 | + return '%'; | |
83 | + } | |
84 | + } | |
85 | + } | |
86 | + } | |
87 | + | |
88 | + @Override | |
89 | + public void close() throws IOException { | |
90 | + ins.close(); | |
91 | + } | |
92 | + | |
93 | +} |
@@ -0,0 +1,61 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.BufferedInputStream; | |
19 | +import java.io.IOException; | |
20 | +import java.io.InputStream; | |
21 | +import java.nio.ByteBuffer; | |
22 | +import java.nio.channels.SocketChannel; | |
23 | + | |
24 | +/** | |
25 | + * | |
26 | + * | |
27 | + * | |
28 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
29 | + */ | |
30 | +public class ResourceEcrire implements Ecrire { | |
31 | + | |
32 | + private String path; | |
33 | + | |
34 | + public ResourceEcrire(String path) { | |
35 | + this.path = path; | |
36 | + } | |
37 | + | |
38 | + @Override | |
39 | + public void write(SocketChannel c) throws IOException { | |
40 | + byte[] a = new byte[1024]; | |
41 | + InputStream ins = null; | |
42 | + int l; | |
43 | + | |
44 | + try { | |
45 | + ins = HTTPServer.class.getResourceAsStream("/" + path); | |
46 | + if(ins == null) { | |
47 | + HTTPUtils.putNotFound(c); | |
48 | + } else { | |
49 | + HTTPUtils.putOk(c); | |
50 | + HTTPUtils.put(c, "\r\n"); | |
51 | + ins = new BufferedInputStream(ins); | |
52 | + while((l = ins.read(a)) >= 0) { | |
53 | + c.write(ByteBuffer.wrap(a, 0, l)); | |
54 | + } | |
55 | + } | |
56 | + } finally { | |
57 | + if(ins != null) ins.close(); | |
58 | + } | |
59 | + } | |
60 | + | |
61 | +} |
@@ -0,0 +1,201 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.ByteArrayInputStream; | |
19 | +import java.io.IOException; | |
20 | +import java.net.InetSocketAddress; | |
21 | +import java.nio.ByteBuffer; | |
22 | +import java.nio.channels.SelectionKey; | |
23 | +import java.nio.channels.Selector; | |
24 | +import java.nio.channels.ServerSocketChannel; | |
25 | +import java.nio.channels.SocketChannel; | |
26 | +import java.nio.charset.Charset; | |
27 | +import java.util.HashMap; | |
28 | +import java.util.Iterator; | |
29 | +import java.util.Map; | |
30 | +import java.util.regex.Matcher; | |
31 | +import java.util.regex.Pattern; | |
32 | + | |
33 | +/** | |
34 | + * | |
35 | + * | |
36 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
37 | + */ | |
38 | +public class HTTPServer { | |
39 | + | |
40 | + private static final int PORT = 9393; | |
41 | + private static final int BUFSIZE = 1024; | |
42 | + | |
43 | + private static Pattern PN1 = Pattern.compile( | |
44 | + "(/([^\\?]+/)*([^/\\.\\?]+(\\.([^/\\.\\?]+))?)?)(\\?.*)?$"); | |
45 | + | |
46 | + static HTTPLogger log = new HTTPLoggerFactory.L(); | |
47 | + | |
48 | + private Map<SelectionKey, HTTPRequestContinuableParser> wait = | |
49 | + new HashMap<SelectionKey, HTTPRequestContinuableParser>(); | |
50 | + private Map<SelectionKey, Ecrire> ecrire = | |
51 | + new HashMap<SelectionKey, Ecrire>(); | |
52 | + private Map<String, HTTPDispatch> dispatchers = | |
53 | + new HashMap<String, HTTPDispatch>(); | |
54 | + private Charset cset; | |
55 | + private String root; | |
56 | + | |
57 | + /** | |
58 | + * | |
59 | + * @param root | |
60 | + */ | |
61 | + public HTTPServer(String root) { | |
62 | + this.root = root; | |
63 | + this.cset = Charset.defaultCharset(); | |
64 | + } | |
65 | + | |
66 | + Ecrire dispatch(SelectionKey k, HTTPRequest q) throws IOException { | |
67 | + HTTPDispatch d; | |
68 | + Class<?> c; | |
69 | + Matcher m; | |
70 | + String s; | |
71 | + | |
72 | + log.finest("method: %s", q.getMethod()); | |
73 | + log.finest("path: %s", q.getPath()); | |
74 | + log.finest("version: %s", q.getVersion()); | |
75 | + try { | |
76 | + s = q.getPath(); | |
77 | + if(!(m = PN1.matcher(s)).find()) { | |
78 | + log.finest("400: %s", s); | |
79 | + return HTTPUtils.MESSAGE400; | |
80 | + } else if(m.group(3) == null) { | |
81 | + // index.html | |
82 | + log.finest("directory: %s", s); | |
83 | + return new ResourceEcrire(root + s + "index.html"); | |
84 | + } else if(m.group(4) != null) { | |
85 | + // resource | |
86 | + log.finest("file: %s", s); | |
87 | + return new ResourceEcrire(root + s); | |
88 | + } else { | |
89 | + // dynamic | |
90 | + log.finest("dynamic: %s", s); | |
91 | + s = (root + s).replace('/', '.'); | |
92 | + c = Class.forName(s); | |
93 | + if((d = dispatchers.get(s)) == null) { | |
94 | + d = (HTTPDispatch)c.newInstance(); | |
95 | + dispatchers.put(s, d); | |
96 | + } | |
97 | + return new DynamicEcrire(d, q, c); | |
98 | + } | |
99 | + } catch(Exception e) { | |
100 | + log.finest("exception read(): %s", e.toString()); | |
101 | + return HTTPUtils.MESSAGE500; | |
102 | + } | |
103 | + } | |
104 | + | |
105 | + void accept(Selector s, | |
106 | + ServerSocketChannel c) throws IOException { | |
107 | + SocketChannel d; | |
108 | + | |
109 | + d = c.accept(); | |
110 | + d.configureBlocking(false); | |
111 | + d.register(s, SelectionKey.OP_READ); | |
112 | + } | |
113 | + | |
114 | + void read(SelectionKey k) throws IOException { | |
115 | + SocketChannel c = (SocketChannel)k.channel(); | |
116 | + ByteBuffer b = ByteBuffer.allocate(BUFSIZE); | |
117 | + HTTPRequestContinuableParser s = null; | |
118 | + HTTPRequest q; | |
119 | + Ecrire w; | |
120 | + byte[] a; | |
121 | + | |
122 | + s = wait.get(k); | |
123 | + while(c.read(b) > 0) { | |
124 | + if(s == null) { | |
125 | + s = new HTTPRequestContinuableParser(cset); | |
126 | + wait.put(k, s); | |
127 | + } | |
128 | + b.flip(); a = new byte[b.limit()]; b.get(a); | |
129 | + log.finest("\"%s\"", new String(a, cset)); | |
130 | + s.readPartial(new ByteArrayInputStream(a)); | |
131 | + } | |
132 | + | |
133 | + if(s == null) { | |
134 | + k.cancel(); c.close(); | |
135 | + } else { | |
136 | + log.finest("read end of request"); | |
137 | + wait.remove(k); | |
138 | + q = s.getRequest(); | |
139 | + w = dispatch(k, q); | |
140 | + k.interestOps(k.interestOps() | SelectionKey.OP_WRITE); | |
141 | + ecrire.put(k, w); | |
142 | + log.finest("ready to response"); | |
143 | + } | |
144 | + } | |
145 | + | |
146 | + void write(SelectionKey k) throws IOException { | |
147 | + SocketChannel c = (SocketChannel)k.channel(); | |
148 | + Ecrire w; | |
149 | + | |
150 | + try { | |
151 | + if((w = ecrire.get(k)) != null) { | |
152 | + log.finest("ecrire found"); | |
153 | + w.write(c); | |
154 | + } else { | |
155 | + log.finest("ecrire not found"); | |
156 | + } | |
157 | + log.finest("responded"); | |
158 | + } finally { | |
159 | + k.cancel(); c.close(); | |
160 | + } | |
161 | + } | |
162 | + | |
163 | + public void serve() throws IOException { | |
164 | + ServerSocketChannel c = null; | |
165 | + Iterator<SelectionKey> i; | |
166 | + SelectionKey k; | |
167 | + Selector s; | |
168 | + | |
169 | + try { | |
170 | + s = Selector.open(); | |
171 | + c = ServerSocketChannel.open(); | |
172 | + c.configureBlocking(false); | |
173 | + c.socket().bind(new InetSocketAddress(PORT)); | |
174 | + c.register(s, SelectionKey.OP_ACCEPT); | |
175 | + while(s.select() > 0) { | |
176 | + try { | |
177 | + i = s.selectedKeys().iterator(); | |
178 | + while(i.hasNext()) { | |
179 | + k = i.next(); i.remove(); | |
180 | + if(!k.isValid()) { | |
181 | + // do nothing | |
182 | + } else if(k.isAcceptable()) { | |
183 | + accept(s, (ServerSocketChannel)k.channel()); | |
184 | + } else if(k.isReadable()) { | |
185 | + log.finest("begin to read"); | |
186 | + read(k); | |
187 | + } else if(k.isWritable()) { | |
188 | + log.finest("begin to write"); | |
189 | + write(k); | |
190 | + } | |
191 | + } | |
192 | + } catch(Exception e) { | |
193 | + e.printStackTrace(System.err); | |
194 | + } | |
195 | + } | |
196 | + } finally { | |
197 | + if(c != null && c.isOpen()) c.close(); | |
198 | + } | |
199 | + } | |
200 | + | |
201 | +} |
@@ -0,0 +1,62 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.text.SimpleDateFormat; | |
19 | +import java.util.logging.Level; | |
20 | + | |
21 | +/** | |
22 | + * | |
23 | + * | |
24 | + * | |
25 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
26 | + */ | |
27 | +public class HTTPLoggerFactory { | |
28 | + | |
29 | + static class L extends HTTPLogger { | |
30 | + | |
31 | + public void log(Level l, String format, Object... os) { | |
32 | + java.util.Date d; | |
33 | + | |
34 | + if(level.intValue() > l.intValue()) { | |
35 | + // do nothing | |
36 | + } else { | |
37 | + d = new java.util.Date(); | |
38 | + System.err.print(FMT.format(d)); | |
39 | + System.err.print(" :"); | |
40 | + System.err.print(l.toString()); | |
41 | + System.err.print(" :"); | |
42 | + System.err.format(format, os); | |
43 | + System.err.println(); | |
44 | + } | |
45 | + } | |
46 | + | |
47 | + }; | |
48 | + | |
49 | + private static final SimpleDateFormat FMT = | |
50 | + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); | |
51 | + | |
52 | + private static HTTPLogger log = new L(); | |
53 | + | |
54 | + /** | |
55 | + * | |
56 | + * @return | |
57 | + */ | |
58 | + public static HTTPLogger getInstance() { | |
59 | + return log; | |
60 | + } | |
61 | + | |
62 | +} |
@@ -0,0 +1,100 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.util.Collections; | |
19 | +import java.util.LinkedHashMap; | |
20 | +import java.util.Map; | |
21 | + | |
22 | +/** | |
23 | + * | |
24 | + * | |
25 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
26 | + */ | |
27 | +public class HTTPRequest { | |
28 | + | |
29 | + private String method, path, version; | |
30 | + private Map<String, String> headers = | |
31 | + new LinkedHashMap<String, String>(); | |
32 | + private Map<String, String> parameters = | |
33 | + new LinkedHashMap<String, String>(); | |
34 | + | |
35 | + // | |
36 | + HTTPRequest(String m, String a, String v, Map<String, String> h, | |
37 | + Map<String, String> p) { | |
38 | + method = m; | |
39 | + path = a; | |
40 | + version = v; | |
41 | + headers = h; | |
42 | + parameters = p; | |
43 | + } | |
44 | + | |
45 | + /** | |
46 | + * @return the method | |
47 | + */ | |
48 | + public String getMethod() { | |
49 | + return method; | |
50 | + } | |
51 | + | |
52 | + /** | |
53 | + * @return the path | |
54 | + */ | |
55 | + public String getPath() { | |
56 | + return path; | |
57 | + } | |
58 | + | |
59 | + /** | |
60 | + * @return the version | |
61 | + */ | |
62 | + public String getVersion() { | |
63 | + return version; | |
64 | + } | |
65 | + | |
66 | + /** | |
67 | + * | |
68 | + * @param key | |
69 | + * @return | |
70 | + */ | |
71 | + public String getHeader(String key) { | |
72 | + return headers.get(key); | |
73 | + } | |
74 | + | |
75 | + /** | |
76 | + * | |
77 | + * @param key | |
78 | + * @return | |
79 | + */ | |
80 | + public String getParameter(String key) { | |
81 | + return parameters.get(key); | |
82 | + } | |
83 | + | |
84 | + /** | |
85 | + * | |
86 | + * @return | |
87 | + */ | |
88 | + public Map<String, String> getHeaders() { | |
89 | + return Collections.unmodifiableMap(headers); | |
90 | + } | |
91 | + | |
92 | + /** | |
93 | + * | |
94 | + * @return | |
95 | + */ | |
96 | + public Map<String, String> getParameters() { | |
97 | + return Collections.unmodifiableMap(parameters); | |
98 | + } | |
99 | + | |
100 | +} |
@@ -0,0 +1,35 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | +import java.nio.channels.SocketChannel; | |
20 | + | |
21 | +/** | |
22 | + * | |
23 | + * | |
24 | + * | |
25 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
26 | + */ | |
27 | +public interface Ecrire { | |
28 | + | |
29 | + /** | |
30 | + * | |
31 | + * @param c | |
32 | + */ | |
33 | + public void write(SocketChannel c) throws IOException; | |
34 | + | |
35 | +} |
@@ -0,0 +1,249 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | +import java.io.InputStream; | |
20 | +import java.nio.charset.Charset; | |
21 | +import java.util.LinkedHashMap; | |
22 | +import java.util.Map; | |
23 | +import java.util.regex.Matcher; | |
24 | +import java.util.regex.Pattern; | |
25 | + | |
26 | +/** | |
27 | + * | |
28 | + * | |
29 | + * | |
30 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
31 | + */ | |
32 | +public class HTTPRequestContinuableParser { | |
33 | + | |
34 | + private static enum S { INIT, HEAD, POST, DONE } | |
35 | + | |
36 | + private static Pattern PTN = Pattern.compile( | |
37 | + "([A-Z]+) +([^ ]+) +HTTP/([01].[0-9])"); | |
38 | + | |
39 | + private Map<String, String> headers = | |
40 | + new LinkedHashMap<String, String>(); | |
41 | + private Map<String, String> parameters = | |
42 | + new LinkedHashMap<String, String>(); | |
43 | + | |
44 | + private URLEncodedInputStream ins; | |
45 | + private StringBuffer buf = new StringBuffer(); | |
46 | + private Matcher match = null; | |
47 | + private String key, val; | |
48 | + private Charset enc; | |
49 | + private S state = S.INIT; | |
50 | + private int etat = 0; | |
51 | + | |
52 | + /** | |
53 | + * | |
54 | + * @param encoding | |
55 | + */ | |
56 | + public HTTPRequestContinuableParser(Charset encoding) { | |
57 | + ins = new URLEncodedInputStream(); | |
58 | + key = val = null; | |
59 | + enc = encoding; | |
60 | + } | |
61 | + | |
62 | + private void completeString() throws IOException { | |
63 | + String s; | |
64 | + | |
65 | + s = buf.toString(); | |
66 | + s = new String(s.getBytes("ISO-8859-1"), enc); | |
67 | + if(!(match = PTN.matcher(s)).matches()) { | |
68 | + throw new IOException(); | |
69 | + } | |
70 | + buf = new StringBuffer(); | |
71 | + etat = 0; | |
72 | + } | |
73 | + | |
74 | + boolean readString() throws IOException { | |
75 | + int c; | |
76 | + | |
77 | + if(ins == null) throw new IOException(); | |
78 | + for(; true; etat = c) { | |
79 | + if((c = ins.read()) < 0) return false; | |
80 | + switch(etat) { | |
81 | + case '\r': | |
82 | + if(c == '\n') { | |
83 | + completeString(); | |
84 | + state = S.HEAD; | |
85 | + return true; | |
86 | + } else { | |
87 | + buf.append(etat).appendCodePoint(c); | |
88 | + break; | |
89 | + } | |
90 | + default: | |
91 | + if(c != '\r') buf.appendCodePoint(c); | |
92 | + break; | |
93 | + } | |
94 | + } | |
95 | + } | |
96 | + | |
97 | + private boolean completeHeader() throws IOException { | |
98 | + if(key != null) { | |
99 | + val = buf.toString().trim(); | |
100 | + } else if(buf.length() > 0) { | |
101 | + key = buf.toString().trim(); | |
102 | + } else if(match.group(1).equals("POST")) { | |
103 | + state = S.POST; etat = 0; | |
104 | + return true; | |
105 | + } else { | |
106 | + state = S.DONE; etat = 0; | |
107 | + return true; | |
108 | + } | |
109 | + headers.put(key, val); | |
110 | + key = val = null; | |
111 | + buf = new StringBuffer(); | |
112 | + return false; | |
113 | + } | |
114 | + | |
115 | + boolean readHeader() throws IOException { | |
116 | + int c; | |
117 | + | |
118 | + if(ins == null) { | |
119 | + state = S.DONE; | |
120 | + return false; | |
121 | + } | |
122 | + | |
123 | + for(; true; etat = c) { | |
124 | + if((c = ins.read()) < 0) return false; | |
125 | + switch(etat) { | |
126 | + case '\r': | |
127 | + if(c != '\n') { | |
128 | + buf.appendCodePoint(etat).appendCodePoint(c); | |
129 | + break; | |
130 | + } else if(completeHeader()) { | |
131 | + return true; | |
132 | + } | |
133 | + break; | |
134 | + case ':': | |
135 | + if(key == null) { | |
136 | + key = buf.toString().trim(); | |
137 | + buf = new StringBuffer(); | |
138 | + } | |
139 | + buf.appendCodePoint(c); | |
140 | + break; | |
141 | + default: | |
142 | + if(c != '\r' && !(c == ':' && key == null)) { | |
143 | + buf.appendCodePoint(c); | |
144 | + } | |
145 | + break; | |
146 | + } | |
147 | + } | |
148 | + } | |
149 | + | |
150 | + private boolean completeParameters() throws IOException { | |
151 | + if(key != null) { | |
152 | + val = buf.toString().trim(); | |
153 | + } else if(buf.length() > 0) { | |
154 | + key = buf.toString().trim(); | |
155 | + } else { | |
156 | + state = S.DONE; etat = 0; | |
157 | + return true; | |
158 | + } | |
159 | + parameters.put(key, val == null ? "" : val); | |
160 | + key = val = null; | |
161 | + buf = new StringBuffer(); | |
162 | + return false; | |
163 | + } | |
164 | + | |
165 | + boolean readParameters() throws IOException { | |
166 | + int c; | |
167 | + | |
168 | + for(; true; etat = c) { | |
169 | + if((c = ins.read()) < 0) return false; | |
170 | + switch(etat) { | |
171 | + case '\r': | |
172 | + if(c != '\n') { | |
173 | + buf.appendCodePoint(etat).appendCodePoint(c); | |
174 | + } else if(completeParameters()) { | |
175 | + return true; | |
176 | + } | |
177 | + break; | |
178 | + case '=': | |
179 | + if(key == null) { | |
180 | + key = buf.toString().trim(); | |
181 | + buf = new StringBuffer(); | |
182 | + } | |
183 | + buf.appendCodePoint(c); | |
184 | + break; | |
185 | + case '&': | |
186 | + if(key != null) { | |
187 | + val = buf.toString().trim(); | |
188 | + } else { | |
189 | + key = buf.toString().trim(); | |
190 | + } | |
191 | + parameters.put(key, val == null ? "" : val); | |
192 | + key = val = null; | |
193 | + buf = new StringBuffer().appendCodePoint(c); | |
194 | + break; | |
195 | + default: | |
196 | + if(c != '\r' && c != '&' && c != '=') { | |
197 | + buf.appendCodePoint(c); | |
198 | + } | |
199 | + break; | |
200 | + } | |
201 | + } | |
202 | + } | |
203 | + | |
204 | + /** | |
205 | + * | |
206 | + * @return | |
207 | + */ | |
208 | + public boolean isDone() { | |
209 | + return state.equals(S.DONE); | |
210 | + } | |
211 | + | |
212 | + /** | |
213 | + * | |
214 | + * @return | |
215 | + */ | |
216 | + public HTTPRequest getRequest() throws IOException { | |
217 | + switch(state) { | |
218 | + case INIT: completeString(); break; | |
219 | + case HEAD: completeHeader(); break; | |
220 | + case POST: completeParameters(); break; | |
221 | + case DONE: break; | |
222 | + } | |
223 | + state = S.DONE; | |
224 | + return new HTTPRequest(match.group(1), match.group(2), | |
225 | + match.group(3), headers, parameters); | |
226 | + } | |
227 | + | |
228 | + /** | |
229 | + * | |
230 | + * @param ins | |
231 | + * @throws IOException | |
232 | + */ | |
233 | + public void readPartial(InputStream in) throws IOException { | |
234 | + boolean b = true; | |
235 | + | |
236 | + ins.newInputStream(in); | |
237 | + while(b) { | |
238 | + switch(state) { | |
239 | + case INIT: b = readString(); break; | |
240 | + case HEAD: b = readHeader(); break; | |
241 | + case POST: b = readParameters(); break; | |
242 | + case DONE: | |
243 | + while(ins.read() >= 0); | |
244 | + return; | |
245 | + } | |
246 | + } | |
247 | + } | |
248 | + | |
249 | +} |
@@ -0,0 +1,39 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | + | |
20 | +/** | |
21 | + * | |
22 | + * | |
23 | + * | |
24 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
25 | + */ | |
26 | +public final class Zhttpd { | |
27 | + | |
28 | + public static void main(String[] args) { | |
29 | + HTTPServer s; | |
30 | + | |
31 | + try { | |
32 | + s = new HTTPServer(args[0].replace('.', '/')); | |
33 | + s.serve(); | |
34 | + } catch(IOException e) { | |
35 | + throw new RuntimeException(e); | |
36 | + } | |
37 | + } | |
38 | + | |
39 | +} |
@@ -0,0 +1,91 @@ | ||
1 | +/* | |
2 | + * Copyright 2013 Yuichiro Moriguchi | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package net.morilib.httpd; | |
17 | + | |
18 | +import java.io.IOException; | |
19 | +import java.io.PrintWriter; | |
20 | +import java.lang.reflect.InvocationTargetException; | |
21 | +import java.lang.reflect.Method; | |
22 | +import java.nio.channels.Channels; | |
23 | +import java.nio.channels.SocketChannel; | |
24 | +import java.nio.charset.CharsetEncoder; | |
25 | + | |
26 | +/** | |
27 | + * | |
28 | + * | |
29 | + * | |
30 | + * @author MORIGUCHI, Yuichiro 2013/09/28 | |
31 | + */ | |
32 | +public class DynamicEcrire implements Ecrire { | |
33 | + | |
34 | + private HTTPDispatch dispatch; | |
35 | + private HTTPRequest request; | |
36 | + private Class<?> classe; | |
37 | + | |
38 | + /** | |
39 | + * | |
40 | + * @param d | |
41 | + * @param q | |
42 | + * @param k | |
43 | + */ | |
44 | + public DynamicEcrire(HTTPDispatch d, HTTPRequest q, Class<?> k) { | |
45 | + dispatch = d; | |
46 | + request = q; | |
47 | + classe = k; | |
48 | + } | |
49 | + | |
50 | + @Override | |
51 | + public void write(SocketChannel c) throws IOException { | |
52 | + PrintWriter p = null; | |
53 | + CharsetEncoder o; | |
54 | + Method t; | |
55 | + | |
56 | + o = dispatch.getCharset().newEncoder(); | |
57 | + p = new PrintWriter(Channels.newWriter(c, o, -1)); | |
58 | + try { | |
59 | + HTTPServer.log.finest("DynamicEcrire"); | |
60 | + t = classe.getMethod("execute", HTTPRequest.class, | |
61 | + PrintWriter.class); | |
62 | + HTTPUtils.putOk(c); | |
63 | + HTTPUtils.put(c, o.toString()); | |
64 | + HTTPUtils.put(c, "\r\n\r\n"); | |
65 | + t.invoke(dispatch, request, p); | |
66 | + } catch(SecurityException e) { | |
67 | + HTTPServer.log.finest("exception write(): %s", | |
68 | + e.toString()); | |
69 | + HTTPUtils.puterror(c, e); | |
70 | + } catch(NoSuchMethodException e) { | |
71 | + HTTPServer.log.finest("exception write(): %s", | |
72 | + e.toString()); | |
73 | + HTTPUtils.puterror(c, e); | |
74 | + } catch(IllegalArgumentException e) { | |
75 | + HTTPServer.log.finest("exception write(): %s", | |
76 | + e.toString()); | |
77 | + HTTPUtils.puterror(c, e); | |
78 | + } catch(IllegalAccessException e) { | |
79 | + HTTPServer.log.finest("exception write(): %s", | |
80 | + e.toString()); | |
81 | + HTTPUtils.puterror(c, e); | |
82 | + } catch(InvocationTargetException e) { | |
83 | + HTTPServer.log.finest("exception write(): %s", | |
84 | + e.toString()); | |
85 | + HTTPUtils.putApplicationError(p, e.getCause()); | |
86 | + } finally { | |
87 | + p.flush(); | |
88 | + } | |
89 | + } | |
90 | + | |
91 | +} |