概要

前回のプログラムで画像を表示できるようになりましたが、さらにいろんなファイルに対応できるようにしましょう。とはいえ、ファイル形式が増えるたびにif文を増やすのもバカらしいのでMapを使ってどんなファイルを送信するのかだけクライアントに伝えあとはファイルの中身をすべて送信してしまいましょう。またHTMLに画像や音声、動画が含まれるたびに通信が発生しますが、これを1つのThreadでやるとファイルを読み込み→クライアントに送信が終わらなければ次の通信処理にいけないため、クライアントから接続があったらその度に新しいThreadを作ることにしました。

ソースコード

import java.net.Socket;
import java.net.ServerSocket;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import java.io.PrintStream;
import java.io.BufferedOutputStream;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.nio.charset.StandardCharsets;

import java.util.Map;
import java.util.HashMap;

import java.io.IOException;

public class HttpServerSample{
    //ファイルの拡張子とMIMEタイプを関連付けるMap
    private static final Map<String, String> MIME_MAP;
    
    static{
        MIME_MAP = new HashMap<>();
        
        //HTMLやCSS、JavaScriptのMIMEタイプ
        MIME_MAP.put("htm", "text/html");
        MIME_MAP.put("html", "text/html");
        MIME_MAP.put("css", "text/css");
        MIME_MAP.put("js", "text/javascript");

        //画像のMIMEタイプ
        MIME_MAP.put("jpg", "image/jpeg");
        MIME_MAP.put("jpeg", "image/jpeg");
        MIME_MAP.put("png", "image/png");
        
        //音声のMIMEタイプ
        MIME_MAP.put("wav", "audio/wav");
        MIME_MAP.put("mp3", "audio/mpeg");
        MIME_MAP.put("aac", "audio/aac");
        
        //動画のMIMEタイプ
        MIME_MAP.put("mpeg", "video/mpeg");
        MIME_MAP.put("mp4", "video/mp4");
    }
    
    public static void main(String... args){
        //ポート12435でServerSocket作成
        try(var server = new ServerSocket(12435)){
            HttpServerSample.accept(server);
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    private static void accept(ServerSocket server) throws IOException{
        while(true){
            //クライアントからの接続を待機する
            var socket = server.accept();
            
            //クライアントとの通信用のThreadを作る
            var thread = new Thread(() -> {
                try(socket){
                    //クライアントから接続されたときの処理
                    HttpServerSample.connectHttp(socket);
                }catch(IOException ex){
                    ex.printStackTrace();
                }
            });
                
            thread.start();
        }
    }

    private static void connectHttp(Socket socket) throws IOException{
        try(
            var in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
            var out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()), true);
        ){
            String line = in.readLine();

            if(line != null){
                var tokens = line.split(" ");
                var fileName = tokens.length == 3 ? tokens[1] : "index.html";

                if(fileName.equals("/")){
                    fileName = "index.html";
                }

                System.out.printf("Request FileName: %s\n", fileName);

                while((line = in.readLine()) != null){
                    System.out.println(line);

                    if(line.isEmpty()){
                        break;
                    }
                }

                HttpServerSample.printFile(out, fileName);
            }
        }
    }

    private static void printFile(PrintStream out, String fileName) throws IOException{
        var path = Paths.get("www", fileName);

        if(Files.exists(path)){
            String extension = HttpServerSample.getFileExtension(path).toLowerCase();

            if(HttpServerSample.MIME_MAP.containsKey(extension)){
                System.out.println("MIME Type: " + HttpServerSample.MIME_MAP.get(extension));
                
                //拡張子から画像として判断し画像ファイルを読み込んでそのまま返す
                var content = Files.readAllBytes(path);

                out.println("HTTP/1.1 200 OK");
                out.println("Content-Type: " + HttpServerSample.MIME_MAP.get(extension));
                out.println("Content-Length: " + content.length);
                out.println("");

                out.write(content);
            }else{
                //拡張子で判断できなければとりあえずHTMLで返す
                out.println("HTTP/1.1 200 OK");
                out.println("Content-Type: text/html; charset=UTF-8");
                out.println();
                Files.lines(path).forEach(out::println);
            }
        }else{
            //ファイルが見つからなければ404を返す
            out.println("HTTP/1.1 404 Not Found");
            out.println("Content-Type: text/html; charset=UTF-8");
            out.println();
            out.println("<html><title><head>404 Not Found</head></title><body><h1>404 Not Found</h1></body></html>");

            System.err.printf("%s is Not Found", path.toAbsolutePath().toString());
        }
    }
    
    private static String getFileExtension(Path path) {
        String fileName = path.getFileName().toString();
        int index = fileName.lastIndexOf('.'); //ファイル名の最後のドットの位置を調べる

        //ファイル名にドットが無い場合は拡張子なしとして空文字列を返す
        if(index == -1){
            return ""; // 拡張子なし
        }

        //ファイル名にドットがあればそれ以降を返す
        return fileName.substring(index + 1);
    }
}

実行結果

今回の変更により画像だけでなく音声や動画にも対応しました。そしてクライアントとの通信用のThreadを作ることでファイルの送信が終わるのを待つことなく次の接続に備えることができ、待たされる時間の退縮にもつながっています。このThreadの効果を知りたい場合は新しいThreadを作ることなくHttpServerSample.connectHttp(socket);を呼び出して動画などの大き目のファイルをHTMLで読み込ませてみましょう。