HTTPリクエストの解釈

先ほどは、リクエスト内容に寄らず常に固定の内容を返すHTTPサーバーを実装しました。 次は、リクエスト内容を読み込み、その内容を解釈できるように改良を加えていきましょう。

コネクションからのデータの読み込みとデータの処理を同時に行うと処理が煩雑になります。 そのため、はじめにイテレータを用いてコネクションからの入力処理を分離しましょう。 イテレータは、繰り返し可能なオブジェクトを表す概念で、要素を順次一つずつ取り出すことができます。 ここでは、コネクションからの行の読み込みをクラスを用いてイテレータ化します。

コネクションから1行ずつ読み込むための処理を HeaderReader クラスとして実装します。 HeaderReader クラスは IteratorIterable の2つのインタフェースを implements します。

class HeaderReader implements Iterable<String>, Iterator<String> {
  Client client;
  boolean stop = false;

  HeaderReader(Client client) {
    this.client = client;
  }

  Iterator<String> iterator() {
    return this;
  }

  boolean hasNext() {
    return !this.stop;
  }

  String next() {
    while (this.client.active()) {
      if (this.client.available() > 0) {
        String line = this.client.readStringUntil('\n');
        if (line != null) {
          if (line.equals("\r\n")) {
            this.stop = true;
          }
          return line;
        }
      }
    }
    return null;
  }
}

Iterator インタフェースは hasNextnext の2つのメソッドを持ちます。 hasNext メソッドは、イテレータが次の要素を持つかどうかの真理値を返します。 next メソッドはイテレータから実際に次の値を取り出します。

Iterable インタフェースは iterator メソッドを実装し、何らかのイテレーター(Iterator インタフェースを implements したオブジェクト)を返します。 ここでは、HeaderReader クラス自体がイテレーターであるため、HeaderReaderiterator メソッドは this を返すだけです。

HeaderReader を利用して、コネクションを処理する Handler クラスを実装しましょう。

class Handler extends Thread {
  Client client;

  Handler(Client client) {
    this.client = client;
  }

  void run() {
    HeaderReader reader = new HeaderReader(this.client);

    String[] request = split(reader.next().trim(), ' ');
    println("Method : " + request[0]);
    println("Path : " + request[1]);
    println("Version : " + request[2]);

    for (String line : reader) {
      String[] header = split(line.trim(), ": ");
      if (header.length == 2) {
        println(header[0] + " : " + header[1]);
      }
    }

    this.client.write("HTTP/1.1 200 OK\r\n");
    this.client.write("\r\n");
    this.client.write("hello\r\n");
    this.client.stop();
  }
}

run メソッド内で、はじめに HeaderReader のインスタンスを作成します。 そして next メソッドを使用して、リクエスト行を取り出します。 リクエスト行は、メソッドとパス、HTTPバージョンの3つをスペース区切りで含みます。

リクエスト行に続き、リクエストヘッダーが1行ずつ続きます。 拡張for文によって、続くヘッダーを HeaderReader から1行ずつ取り出します。 各ヘッダーには、ヘッダー名と値が「: 」で区切られて渡されます。 ヘッダーを1行ずつ読み込み、ヘッダー名と値を取り出してコンソールに出力していきます。

サーバープログラムの全体は以下のようになります。

import java.util.Iterator;
import processing.net.*;

Server myServer;

void setup() {
  myServer = new Server(this, 80);
}

void draw() {
}

void serverEvent(Server server, Client client) {
  Handler handler = new Handler(client);
  handler.start();
}

class Handler extends Thread {
  Client client;

  Handler(Client client) {
    this.client = client;
  }

  void run() {
    HeaderReader reader = new HeaderReader(this.client);

    String[] request = split(reader.next().trim(), ' ');
    println("Method : " + request[0]);
    println("Path : " + request[1]);
    println("Version : " + request[2]);

    for (String line : reader) {
      String[] header = split(line.trim(), ": ");
      if (header.length == 2) {
        println(header[0] + " : " + header[1]);
      }
    }

    this.client.write("HTTP/1.1 200 OK\r\n");
    this.client.write("\r\n");
    this.client.write("hello\r\n");
    this.client.stop();
  }
}

class HeaderReader implements Iterable<String>, Iterator<String> {
  Client client;
  boolean stop = false;

  HeaderReader(Client client) {
    this.client = client;
  }

  Iterator<String> iterator() {
    return this;
  }

  boolean hasNext() {
    return !this.stop;
  }

  String next() {
    while (this.client.active()) {
      if (this.client.available() > 0) {
        String line = this.client.readStringUntil('\n');
        if (line != null) {
          if (line.equals("\r\n")) {
            this.stop = true;
          }
          return line;
        }
      }
    }
    return null;
  }
}

results matching ""

    No results matching ""