Skip to content

Instantly share code, notes, and snippets.

@irof
Created September 28, 2012 15:13
Show Gist options
  • Save irof/3800442 to your computer and use it in GitHub Desktop.
Save irof/3800442 to your computer and use it in GitHub Desktop.
不完全なHTTPサーバ
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.10'
}
sourceSets {
main{
java{
srcDir './'
exclude '*Test.java'
}
}
test {
java{
srcDir './'
include '*Test.java'
}
}
}
{ "message" : "Hello, world!" }
Hello, world!
package httpserver;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class HttpServer implements AutoCloseable {
private final int port;
private final ServerSocket server;
private final ExecutorService executor;
private final Path webappDir;
private final Map<String, String> contentTypes;
public HttpServer(Path webappDir, int port) throws IOException {
this.webappDir = webappDir;
this.port = port;
this.server = new ServerSocket();
this.executor = Executors.newSingleThreadExecutor();
this.contentTypes = new HashMap<>();
this.contentTypes.put(".txt", "text/plain; charset=UTF-8");
this.contentTypes.put(".json", "application/json; charset=UTF-8");
}
public void start() {
executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
process();
return null;
}
});
System.out.println("[Server started]");
}
@Override
public void close() throws IOException {
System.out.println("[Server closing]");
server.close();
executor.shutdownNow();
}
private void process() throws IOException {
SocketAddress endpoint = new InetSocketAddress(port);
server.bind(endpoint);
while (Thread.currentThread().isInterrupted() == false
&& server.isClosed() == false) {
try (Socket client = server.accept()) {
handleClientSocket(client);
}
}
}
private void handleClientSocket(Socket client) throws IOException {
//ちょくちょく使うのでインスタンス化しとく
DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
/*
* リクエストを読む
*/
ByteArrayOutputStream data = new ByteArrayOutputStream();
InputStream in = client.getInputStream();
int i;
//リクエストラインを読んで標準出力に書き出す
//今回はGETリクエストが来ることを前提にしているので変数に保持しない
while (-1 != (i = in.read())) {
if (i == '\r') { //CR
in.read(); //LF
System.out.println("[Request line] " + data);
break;
}
data.write(i);
}
String[] requestLine = data.toString().split(" ");
if (requestLine[0].equals("GET") == false) {
//今回はGETリクエスト以外は扱わない
System.out.println(requestLine[0] + "は扱えないメソッド");
String content = "501 Not Implemented";
Writer out = new OutputStreamWriter(client.getOutputStream());
out.write("HTTP/1.0 501 Not Implemented\r\n");
//general-header
out.write("Connection: close\r\n");
out.write("Date: " + df.format(new Date()) + "\r\n");
//response-header
out.write("Server: SimpleHttpServer/0.1\r\n");
//entity-header
out.write("Content-Length: "
+ content.getBytes("UTF-8").length
+ "\r\n");
out.write("Content-Type: text/plain; charset=UTF-8\r\n");
out.write("\r\n");
out.write(content);
out.flush();
return;
}
//リクエストヘッダを読んで標準出力に書き出す
//今回はリクエストヘッダを利用しないので変数に保持しない
data = new ByteArrayOutputStream();
while (-1 != (i = in.read())) {
if (i == '\r') { //CR
in.read(); //LF
System.out.println("[Request header] " + data);
if ((i = in.read()) == '\r') { //CR
in.read(); //LF
break;
}
data = new ByteArrayOutputStream();
}
data.write(i);
}
/*
* リクエストを解析(というほどのことはしていないが)
*/
//Request-URI は abs_path であることを前提にしておく
Path requestPath = webappDir.resolve(requestLine[1].substring(1));
if (Files.notExists(requestPath)) {
//ファイルがなければ404
System.out.println(requestPath + "が見つからない");
String content = "404 Not Found";
Writer out = new OutputStreamWriter(client.getOutputStream());
out.write("HTTP/1.0 404 Not Found\r\n");
//general-header
out.write("Connection: close\r\n");
out.write("Date: " + df.format(new Date()) + "\r\n");
//response-header
out.write("Server: SimpleHttpServer/0.1\r\n");
//entity-header
out.write("Content-Length: "
+ content.getBytes("UTF-8").length
+ "\r\n");
out.write("Content-Type: text/plain; charset=UTF-8\r\n");
out.write("\r\n");
out.write(content);
out.flush();
return;
}
/*
* レスポンスの準備
*/
String lastModified =
df.format(new Date(Files
.getLastModifiedTime(requestPath)
.toMillis()));
byte[] fileContent = Files.readAllBytes(requestPath);
String contentType = null;
String fileName = requestPath.getFileName().toString();
int index = fileName.lastIndexOf('.');
if (index > -1) {
String extension = fileName.substring(index);
contentType = contentTypes.get(extension);
}
//Content-Typeが不明なファイルはoctet-streamにしちゃう
if (contentType == null) {
contentType = "application/octet-stream";
}
/*
* レスポンスを書く
*/
OutputStream responseStream = client.getOutputStream();
Writer out = new OutputStreamWriter(responseStream);
//ステータスライン
out.write("HTTP/1.0 200 OK\r\n");
//レスポンスヘッダ
//general-header
out.write("Connection: close\r\n");
out.write("Date: " + df.format(new Date()) + "\r\n");
//response-header
out.write("Server: SimpleHttpServer/0.1\r\n");
//entity-header
out.write("Content-Length: " + fileContent.length + "\r\n");
out.write("Content-Type: " + contentType + "\r\n");
out.write("Last-Modified: " + lastModified + "\r\n");
out.write("\r\n");
out.flush();
//エンティティボディ
responseStream.write(fileContent);
responseStream.flush();
}
}
package httpserver;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Paths;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class HttpServerTest {
@Test
public void GETリクエストでtxtファイルを貰う() throws Exception {
URL url = new URL("http://localhost:8080/hello.txt");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int statusCode = con.getResponseCode();
assertThat(statusCode, is(200));
System.out.println(con.getHeaderFields());
assertThat(con.getContentType(), is("text/plain; charset=UTF-8"));
try (InputStream in = con.getInputStream()) {
String response = readAll(in);
assertThat(response, is("Hello, world!"));
}
}
@Test
public void GETリクエストでjsonファイルを貰う() throws Exception {
URL url = new URL("http://localhost:8080/hello.json");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int statusCode = con.getResponseCode();
assertThat(statusCode, is(200));
System.out.println(con.getHeaderFields());
assertThat(con.getContentType(), is("application/json; charset=UTF-8"));
try (InputStream in = con.getInputStream()) {
String response = readAll(in);
assertThat(response, is("{ \"message\" : \"Hello, world!\" }"));
}
}
@Test
public void ファイルが見つからなかったら404NotFound() throws Exception {
URL url = new URL("http://localhost:8080/hello.unknown");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
int statusCode = con.getResponseCode();
assertThat(statusCode, is(404));
System.out.println(con.getHeaderFields());
assertThat(con.getContentType(), is("text/plain; charset=UTF-8"));
try (InputStream in = con.getErrorStream()) {
String response = readAll(in);
assertThat(response, is("404 Not Found"));
}
}
@Test
public void POSTリクエストは未実装() throws Exception {
URL url = new URL("http://localhost:8080/hello");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
int statusCode = con.getResponseCode();
assertThat(statusCode, is(501));
System.out.println(con.getHeaderFields());
assertThat(con.getContentType(), is("text/plain; charset=UTF-8"));
try (InputStream in = con.getErrorStream()) {
String response = readAll(in);
assertThat(response, is("501 Not Implemented"));
}
}
/**
* テスト毎にHTTPサーバを立てては落とす賢いヤツ
*/
@ClassRule
public static TestRule serverController = new TestRule() {
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try (HttpServer server =
new HttpServer(Paths.get("./"), 8080)) {
server.start();
base.evaluate();
}
}
};
}
};
private static String readAll(InputStream in) throws IOException {
byte[] b = new byte[8192];
int i;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while (-1 != (i = in.read(b, 0, b.length))) {
out.write(b, 0, i);
}
out.flush();
out.close();
return out.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment