Skip to content

Instantly share code, notes, and snippets.

@relax-more
Last active December 12, 2015 08:29
Show Gist options
  • Save relax-more/4744570 to your computer and use it in GitHub Desktop.
Save relax-more/4744570 to your computer and use it in GitHub Desktop.
[java] Web+DBPress No47より、第3章を写経した HTTPをWebのしくみ webServerを実装する
package main.java.me.relaxmore.study.servertest.server;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
import static java.net.HttpURLConnection.HTTP_OK;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
/**
* HTTPリクエストをハンドリングするクラス。
*/
public class HttpHandler {
private final File documentRoot;
private final String directoryIndex;
public HttpHandler(String path, String directoryIndex) {
documentRoot = new File(path);
this.directoryIndex = directoryIndex;
}
/**
* HTTPリクエストのハンドリングを開始します。<br>
* メソッド毎に処理を振り分けます。<br>
* 対応していないメソッドの場合は Status-Code 501 Not Implemented を送信します。<br>
* リクエストURIで指定されたファイルがオープンできない場合は Status-Code 404 Not Found を送信します。
* @param req リクエストオブジェクト。
* @param res レスポンスオブジェクト。
* @throws IOException 入出力例外が発生。
*/
public void handle(Request req, Response res) throws IOException {
try {
if ("GET".equals(req.getMethod())) {
doGet(req, res);
} else if ("HEAD".equals(req.getMethod())) {
doHead(req, res);
} else {
// 対応していないメソッドの場合
res.sendResponseHeader(HTTP_NOT_IMPLEMENTED);
}
} catch (FileNotFoundException e) {
// 指定されたファイルが見つからない場合
res.sendResponseHeader(HTTP_NOT_FOUND);
}
}
/**
* リクエストURIで指定されたファイルを入力ストリームとして取得します。<br>
* 入力ストリームがオープンできた場合は Status-Code 200 OK を送信します。
* @param req リクエストオブジェクト。
* @param res レスポンスオブジェクト。
* @return 指定されたファイルの入力ストリーム。
* @throws FileNotFoundException リクエストURIで指定されたファイルをオープンできなかった場合に発生。
* @throws IOException 入出力例外が発生。
*/
private InputStream getContentInputStream(Request req, Response res)
throws FileNotFoundException, IOException {
File content = getContent(req.getRequestUri());
InputStream is = new FileInputStream(content);
res.setHeader("Content-Type", Utils.getMediaType(content));
res.setHeader("Content-Length", Long.toString(content.length()));
return is;
}
/**
* リクエストURIで指定されたファイルオブジェクトを取得します。<br>
* 指定されたパスがディレクトリの場合は、ディレクトリインデックスファイルを返します。
* @param requestUri リクエストURI。
* @return ファイルオブジェクト。
*/
private File getContent(URI requestUri) {
File content = new File(documentRoot, requestUri.getPath());
if (content.isDirectory()) {
return new File(content, directoryIndex);
}
return content;
}
/**
* HEADメソッドを処理します。
* @param req リクエストオブジェクト。
* @param res レスポンスオブジェクト。
* @throws IOException
* @throws FileNotFoundException
*/
private void doHead(Request req, Response res) throws IOException,
FileNotFoundException {
getContentInputStream(req, res).close();
res.sendResponseHeader(HTTP_OK);
}
/**
* GETメソッドを処理します。<br>
* リクエストURIで指定されたファイルの内容をメッセージ本体に出力します。
* @param req リクエストオブジェクト。
* @param res レスポンスオブジェクト。
* @throws FileNotFoundException リクエストURIで指定されたファイルをオープンできなかった場合に発生。
* @throws IOException 入出力例外が発生。
*/
private void doGet(Request req, Response res) throws FileNotFoundException,
IOException {
InputStream is = getContentInputStream(req, res);
try {
res.sendResponseHeader(HTTP_OK);
Utils.copy(is, res.getMessageBody());
} finally {
is.close();
}
}
}
package main.java.me.relaxmore.study.servertest.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import main.java.me.relaxmore.study.servertest.server.Request.BadRequestException;
public class MyServer {
private final ServerSocket socket;
private final ExecutorService threads;
private final HttpHandler httpHandler;
public MyServer(int port, int nThreads, String path, String directoryIndex) throws IOException{
socket = new ServerSocket(port);
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress(port));
threads = Executors.newFixedThreadPool(nThreads);
httpHandler = new HttpHandler(path, directoryIndex);
}
public void listen(){
while(true){
try {
final Socket conn = socket.accept();
threads.execute(new Runnable() {
@Override
public void run() {
handler(conn);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handler(Socket conn){
try {
Response res = new Response(conn.getOutputStream());
try {
Request req = new Request(conn.getInputStream());
httpHandler.handle(req, res);
} catch (BadRequestException e) {
e.printStackTrace();
}finally{
if(!conn.isClosed()){
conn.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main (String[] args) throws IOException{
MyServer server = new MyServer(8081, 150, "anyPath", "index.html");
server.listen();
}
}
package main.java.me.relaxmore.study.servertest.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
public class Request {
private final String method;
private final URI requestUri;
public Request(InputStream is) throws IOException, BadRequestException {
// InputStream を Reader に変換
// リクエストヘッダの文字コードはUS-ASCII
final BufferedReader reader = new BufferedReader(new InputStreamReader(is, "US-ASCII"));
final String line = reader.readLine();
if (line == null) {
// 入力が存在しない
throw new BadRequestException();
}
String[] items = line.split(" ");
if (items.length < 2) {
// リクエストラインの構文エラー
throw new BadRequestException();
}
method = items[0];
try {
requestUri = new URI(items[1]);
} catch (URISyntaxException e) {
// リクエストURIの構文エラー
throw new BadRequestException(e);
}
}
public String getMethod() {
return method;
}
public URI getRequestUri() {
return requestUri;
}
public static class BadRequestException extends Exception {
private static final long serialVersionUID = 1L;
public BadRequestException() {
}
public BadRequestException(Throwable cause) {
super(cause);
}
}
}
package main.java.me.relaxmore.study.servertest.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
public class Response {
private static final String CRLF = "¥r¥n";
private final OutputStream os;
private final Map<String, String> headers = new HashMap<String, String>();
/**
* レスポンスヘッダを既に送信したことをあらわす。
*/
private boolean sentResponseHeader = false;
public Response(OutputStream os) {
this.os = os;
setHeader("Server", "MyWebServer/1.0");
setHeader("Connection", "close");
}
public void setHeader(String fieldName, String fieldValue) {
headers.put(fieldName, fieldValue);
}
/**
* レスポンスヘッダをクライアントに送信します。
* @param statusCode HTTPステータスコード。
* @throws IOException レスポンスヘッダを返す際に入出力例外が発生。
*/
public void sendResponseHeader(int statusCode) throws IOException {
// 2回目以降の呼び出しは無視する
if (sentResponseHeader) {
return;
}
sentResponseHeader = true;
// レスポンスヘッダの文字コードはUS-ASCII
Writer writer = new BufferedWriter(new OutputStreamWriter(os, "US-ASCII"));
setHeader("Date", Utils.getDate());
sendStatusLine(writer, statusCode);
sendHeaders(writer);
writer.write(CRLF);
writer.flush();
}
public OutputStream getMessageBody() {
return os;
}
private void sendStatusLine(Writer writer, int statusCode)
throws IOException {
writer.write("HTTP/1.1 " + statusCode + " "
+ Utils.getStatusPhrase(statusCode) + CRLF);
}
private void sendHeaders(Writer writer) throws IOException {
for (Map.Entry<String, String> e : headers.entrySet()) {
writer.write(e.getKey() + ": " + e.getValue() + CRLF);
}
}
}
package main.java.me.relaxmore.study.servertest.server;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
import static java.net.HttpURLConnection.HTTP_OK;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
public final class Utils {
private static final int COPY_BUFFER_SIZE = 8192;
private static final Map<Integer, String> STATUS_PHRASE;
/**
* ステータスフレーズを初期化します。
*/
static {
Map<Integer, String> tmp = new HashMap<Integer, String>();
tmp.put(HTTP_OK, "OK");
tmp.put(HTTP_BAD_REQUEST, "Bad Request");
tmp.put(HTTP_NOT_FOUND, "Not Found");
tmp.put(HTTP_NOT_IMPLEMENTED, "Not Implemented");
tmp.put(HTTP_INTERNAL_ERROR, "Internal Server Error");
STATUS_PHRASE = Collections.unmodifiableMap(tmp);
}
public static void copy(InputStream is, OutputStream os) throws IOException {
InputStream bis = new BufferedInputStream(is);
OutputStream bos = new BufferedOutputStream(os);
byte[] buffer = new byte[COPY_BUFFER_SIZE];
for (int len; (len = bis.read(buffer)) != -1;) {
bos.write(buffer, 0, len);
}
bos.flush();
}
public static String getMediaType(File content) {
return URLConnection.getFileNameMap().getContentTypeFor(content.getName());
}
public static String getStatusPhrase(int statusCode) {
return STATUS_PHRASE.get(statusCode);
}
public static String getDate() {
DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",Locale.US);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(new Date());
}
private Utils() {
}
}
jakarta commons の FileUpload と Java の FileChannel と
http://archive.guma.jp/2009/12/jakarta-commons-fileupload-java-filechannel.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment