HTTP サーバー + TCP ベースのサーブレット コンテナ

序文

このブログで共有しているのは、私が実装した HTTP サーバー + サーブレット コンテナーです。これは、複数の Web アプリケーションのストレージを含む非常に小さな tomcat 内部機能として理解でき、標準ブラウザーと簡単に対話できる HTTP サーバーです。このプロジェクトは、ネットワーク学習の初心者に非常に適しています. ネットワーク伝送、HTTP プロトコル、およびその他の知識をより明確に理解できます. このブログは、ブロガーの個人的な研究とメモのためのものであることをここに宣言します.同時に、このブログの特定の場所に誤記や不適切な理解がある可能性があります. これらの問題については、私を修正してください.

プロジェクトの説明

このプロジェクトは TCP に基づいており、指定された HTTP プロトコルに従って分析データを取得し、URL 上のパスを介して、サーバー内の対応する Web アプリケーションの下で対応するサーブレット処理を見つけ、最後にクライアントに応答を返します。 , 標準を達成するためにブラウザ間の単純な対話のための HTTP サーバー.
このプロジェクトでは、ネットワークの原則と HTTP プロトコルについてある程度の理解が必要です. あまり明確でないブロガーは、HTTP プロトコルとネットワークの原則を参照できます

知識の事前調査に携わる

HTTP プロトコル

HTTP プロトコルは、 TCP に基づくアプリケーション層プロトコルです。その役割は、リソースの要求と応答を処理することです。HTTP
プロトコルの形式
ここに画像の説明を挿入

URL - 本質的に特定の方法で一意の場所のリソース. Web ページにアクセスするブラウザに対応するのは入力 URL です.
標準形式は次のとおりです: プロトコル : // ホスト名[:ポート] / パス / [;パラメータ][?クエリ]
HTTP プロトコルの場合、下図のように URL の各部分を分解します。
ここに画像の説明を挿入

TCP プロトコルとソケット ソケットのプログラミング

HTTP プロトコルはTCP プロトコルに基づいて動作します
。TCP 転送の信頼性のために、TCP はローカル ポートからデータを送信し、最終的に宛先ポートのプロセスにデータを配信します。実装した HTTP サーバーもプロセスに基づいて
、TCPはトランスポート層プロトコルです.TCPがデータを送信するには、TCP接続が合計3つの段階を通過する必要があります.ソケット
ここに画像の説明を挿入
プログラミングはソケットプログラミングとも呼ばれます.TCP、UDPなど.ことで、アプリケーション層がオペレーティング システムを使用して Socket を介してさまざまなサービスを提供できるようにする.このプロジェクトでは、TCP の使用は、JDK によってカプセル化された Socket クラスを通じて実現されます. 合計 2 つの関連するクラス今回は java.net.ServerSocket が使用されます。サーバーはパッシブ接続パーティとして使用され、スリーウェイ ハンドシェイク前のポート監視は、サーバーがjava.net.Socket に接続するために提供するポート (クライアント) を決定するために使用されます。アクティブな接続パーティとして、サーバーはパッシブな接続パーティとして機能し、確立された TCP 接続を作成します。


ServerSocket serverSocket = new ServerSocket(8080);//监听8080端口
Socket socket = serverSocket.accept();//一条TCP连接已建立,三次握手完成
//通信阶段...
socket.close();//该TCP连接断开,四次挥手完成

基本原理の実現

HTTP サーバーの場合

その主な機能は、HTTP 要求を取得して分析し、データを処理して、最後に応答を送信することです。具体的な手順は次のとおりです。

  1. 最初に Socket を使用して TCP 接続を確立します
  2. TCPに基づいて、送信された要求データを取得し、HTTPプロトコルに従ってデータを解析します
  3. 次に、URL の contextPath と servletPath を分析して、サーバーによって処理されるサーブレットを特定します。
  4. 最後に、処理されたデータは Response に組み立てられ、Socket を介してクライアント (ブラウザー) に送り返されます。

サーブレットコンテナの場合

これは主に公式のサーブレット標準を実装するために使用されます. ここではいくつかの一般的な機能のみが実装されており, 基本的にすべて公式のサーブレットのサブセットを実装しています.

内部的には、各 Web アプリケーションは標準の webapp 形式に従って保存され、各プロジェクト用に作成されたサーブレットは特定のディレクトリに保存され、各 Web アプリケーションの構成ファイル (web.conf) を介して対応するファイルに保存されます。サーブレット オブジェクトは、リフレクション メカニズムを使用してインスタンス化され、マップに格納され、HTTP サーバーが要求を処理するときに最終的に呼び出されます。

全体的なワークフロー

全体的なプロセス フレームワーク

  1. 最初に各コンテキストの下でサーブレット オブジェクトを初期化します (スキャン/クラスのロード/インスタンス化/初期化)
    コンテナーに格納されているすべての Web アプリケーションの下のすべての Servlet.init()
  2. TCP サーバー処理
    while(true){ HTTP リクエストを処理する servlet.service 毎回 loop }

  3. 破棄処理を実行する servlet.destroy()

大規模なプロセス フレームワークに基づいて、初期化操作と HTTP 要求操作の処理のためのプロセス フレームワークがさらにあります。

初期化操作の具体的な流れ

  1. すべての Web アプリケーションでコンテキストをスキャンします
  2. 各 Web アプリケーションの下にある Web 構成ファイル (web.conf) を読み取って解析し、各アプリケーションのサーブレットの特定の場所を取得します。
  3. 2 番目のステップに従って Servelt の特定の場所を分析し、必要な ServeltClass を class<?> オブジェクトとしてロードします。
  4. 3 番目のステップでロードされたすべてのサーブレットをインスタンス化する
  5. 各サーブレットの init() メソッドを呼び出して初期化します

HTTP リクエストを処理する特定のプロセス

  1. ソケットからのリクエスト オブジェクト リクエストを解析する
  2. service() メソッドで使用される応答オブジェクトの応答を作成します
  3. リクエスト内の URL を解析して取得した ContextPath に従って、処理のために渡すコンテキストを決定します
  4. リクエスト内のURLを解析して取得したServletPathに応じて、どのサーブレットを処理するかを決定する
  5. 対応する servlet.service(request,response) を呼び出す
  6. HTTP 応答を送信する

プロジェクトモジュール

ここに画像の説明を挿入
このプロジェクトは、プロトコル策定モジュール、HTTP サーバー モジュール、および動的リソース モジュールに分かれています。

  • プロトコル策定モジュール (標準): 主な機能は、一連のプロトコル標準を策定することであり、これらは標準に従ってサーバーによって実装され、Web アプリケーションは標準に従ってサーバーによって提供されるサービスを
    ここに画像の説明を挿入
    使用
    します
    。 HttpServletRequest、HttpServletResponse、HttpSession の抽象クラスとインターフェース、
    および ServletException カスタム例外クラス、Cookie クラス

理論的には、標準モジュールは標準サーブレットを実装する必要があると指定されていますが、公式標準は大きすぎるため、ここでは標準サーブレットのサブセットとして理解できるいくつかの基本標準の実装を参照します。

  • HTTP サーバーモジュール

ここに画像の説明を挿入
このモジュールには以下が含まれます:
HTTP レベルの Request、Response 実装クラス、それらの要求解析および応答送信クラス、およびセッション操作クラス
4 つのローカル処理静的リソース クラス DefaultServlet、3 つのエラー メッセージ クラス MethodNotAllowed、NotFoundServlet、NotLogin
9 対のサーバー さまざまなサービスを提供する初期化と実装のためのクラス、Config、ConfigReader、Context、DefaultContext、ErrorServlet、HTTPServlet、PoolStart、RequestResponseTask、ServletInitialize

  • 動的リソース モジュール:
    ここに画像の説明を挿入
    このモジュールは、動的リソースを設計し、対応する Web アプリケーションで使用するために使用されます.
    ここに画像の説明を挿入
    これは、Web アプリケーションの複数レベルのディレクトリです. Web.conf は、Web アプリケーションの構成ファイルであり、web.xml に似ています.このプロジェクトでは、主にサーバーが提供するさまざまなサービスが正常に実行されるかどうかをテストするために使用される、簡単な結果を持つ Web アプリケーションを作成します。

モジュールの実装

標準開発モジュール

標準定式化モジュールのさまざまなインターフェース抽象クラス間の関係

ここに画像の説明を挿入

サーブレット インターフェースをカスタマイズして、公式サーブレットのサブセット サーブレットを実装する

サーブレットで例外を処理するためのカスタム例外クラス

カスタム ServletRequest インターフェイス

カスタム ServletResponse インターフェース

サーブレットから実装され、service() メソッドと doGet() メソッドを実装するHttpServlet 抽象クラス

ServletRequest から継承されたHTTPServletRequest インターフェイスは、それを基に HTTP リクエストの抽象メソッドを追加します。

HTTPServletResponse インターフェースはServletResponse インターフェースを継承し、HTTP 固有の応答メソッドを追加します

Cookie 情報を処理して Http 要求ヘッダーに格納するための Cookie オブジェクトをカプセル化するカスタム Cookie クラス

HTTP サーバーモジュール

このモジュールには、主に、標準の Request と Response を実装する 5 つの実装クラス、エラーに応答する 3 つの実装クラス、静的リソースを呼び出す 1 つの実装クラス、および特定のサーバー サービスを実装する 9 つの実装クラスが含まれます。

Request 実装クラスは、 HTTPServletRequest インターフェイスを実装し、リクエスト {リクエスト メソッド (メソッド)、パス (RequestURI)、コンテキスト (ContextPath)、サーブレット (ServletPath)、クエリ (パラメーター)、リクエスト ヘッダー (ヘッダー)、Cookie ( CookieList)、セッション データ (セッション) }、および対応する get() メソッド

HTTPServletResponseインターフェイスから実装され、応答 { ステータス コード (status)、
Cookie (CookieList)、ヘッダー (headers)、OutPutSteam (bodyOutputStream)、PrintWriter (bodyPrintWriter) }、および対応する get ( )、set () メソッド

HTTPSession インターフェイスから実装されたHTTPSessionImpl 実装クラスは、Session 属性 {sessionID, sessionData} を作成し、受信した sessionID に応じてローカルでセッションを取得する方法と、セッションをローカルに保存する方法を実現しました。

HttpServer クラスは、メイン メソッドを実装し、サーバーのエントリ ポイントとして、特定のポートの監視を確立し、フレームワーク全体のプロセスに従って初期化および破棄メソッドを作成し、スレッド プールを作成し、HTTP リクエストをそれぞれ処理するスレッドを割り当てます。時間。

ServletInitialize クラスは、サーバーの初期化用のクラスとして、5 つの初期化プロセスに対応する 5 つのメソッドを内部にカプセル化します。

特定の初期化プロセス:

  1. コンテキストを保存している Web アプリケーションのパスに従って、IO を使用してファイルをスキャンし、その下にあるすべてのコンテキスト オブジェクトをマップに保存します。
  2. それぞれの Web 構成ファイルを読み取り、解析します。簡単にするために、Tomcat の web.xml はここでは実装されていませんが、カスタム形式が使用され、解析に有限状態マシンが使用され、ServletPath と ServletName のマップ、マップが取得されます。 ServletName と特定の保存場所の
  3. Context ローディングで必要な ServletClass は Class<?> オブジェクトとして表され、クラス ローディングのために ClassLoader クラスが導入されます. 主な目的は、分離の目的で、各コンテキスト間のクラス ローダが異なることを保証することです.読み込み中のバージョンの競合を防ぎます。読み込みエラーを引き起こします
  4. 必要なサーブレット オブジェクトをインスタンス化し、リフレクション テクノロジを使用してオブジェクトのインスタンス化操作を実行し、各 Class<?> オブジェクトの newInstance() メソッドを呼び出します。
  5. サーブレットの初期化操作を実行し、サーブレット オブジェクトの init() メソッドを呼び出して、サブクラスが re-init() メソッドによって異なる初期化タスクを達成できるようにします。

PoolStart クラスの主な機能は、スレッド プールを作成し、HTTP 処理のために送信された各要求にスレッドを割り当てることです。ここでは、ThreadPoolExecutor を使用してスレッド プールをカスタマイズします

RequestResponseTask クラスは、 HTTP の要求応答処理に使用され、合計 5 つの操作が実行されます。

  1. HTTP リクエストを解析して Request オブジェクトを取得する
  2. 応答オブジェクトを構築する
  3. URL から解析された contextPath に従って、処理するコンテキストを見つけます
  4. URL から解析された servletPath に従って、どのサーブレットがそれを処理するかを調べます
  5. 対応する servlet.service() メソッドを呼び出し、
  6. HTTP 応答の応答オブジェクトを送信

ここに画像の説明を挿入

HttpRequestParser クラスの主な機能は、HTTP 要求を解析し、最終的にそれを Request オブジェクトにカプセル化することです. HTTP 形式に従って、要求行、要求ヘッダー、要求本文を解析し、URL を分割します.

HttpResponseSend クラスの主な機能は、HTTP 応答を結合し、最後に応答をクライアントに書き戻すことです。

Web 構成ファイルを解析して生成された 2 つのマップを 1 つのオブジェクトにカプセル化する構成クラス

ConfigReader クラス。その機能は、ステート マシン テクノロジを使用して Web 構成ファイルを解析することです。

context、name、config、configRead、およびクラス ローダー ClassLoader のさまざまなプロパティを内部的にカプセル化するContext クラス

HttpServlet から継承されたDefaultServlet クラス。その機能は、サーバーの静的リソースを処理することです。

HttpServlet から継承されたNotFoundServlet クラス。その機能は、サーバーが 404 エラーに応答するためのものです。

Httpservlet から継承されたMethodNotAllowed クラス。その機能は、サーバーが 405 エラーに応答するためのものです。

HttpServelt から継承されたNotLogin クラス。その機能は、サーバーが 401 エラーに応答するためのものです。

各エラー応答サーブレットの Map を内部的にカプセル化して保存するErrorServlet クラスは、エラーに応答するときに呼び出すために使用されます。

動的リソース モジュール

その下にはWebアプリケーションの動的リソースがあり、ここにはログイン用のLoginActionServlet、ログイン情報の検証用のProfileActionServlet、簡易翻訳機能用のTranslateServletが実装されています。

キーパーツの詳しい説明

クラスローダーはサーブレットをロードします

クラスローダー

クラスのパーミッション名を使用してクラスのバイナリ バイト ストリーム コード ブロックを取得することは、クラス ローダー (それ自体がクラス) と呼ばれ、メモリにロードされたすべてのクラスの java.lang.Class インスタンス オブジェクトを生成します。

このプロジェクトのサーバー初期化フェーズでは、サーブレット オブジェクトのインスタンス化は、そのクラス ローダー ClassLoader を使用して、クラス ローディング用の ClassLoader クラスを導入することです. 主な目的は、異なるコンテキスト間のクラス ローダー (ClassLoader) が異なる必要があることです.バージョンが競合するのを防ぎます。競合が発生した場合、クラスの読み込みエラーが分離の目的を果たします。

Java クラス ローディング メカニズムに関するリファレンスJava 仮想マシン クラス ローディング メカニズム

public class Context {
    
    
	private final ClassLoader webappClassLoader = Context.class.getClassLoader();
	//得到该类的类加载器
	Map<String,Class<?>> servletClassMap = new HashMap<>();
    public void loadServletClasses() throws ClassNotFoundException {
    
    
         Set<String> servletClassNames = new HashSet<>(config.getServletToServletClassNameMap().values());
         //servletClassNames 得到该context下的所有servlet类名称
         for(String servletClassName : servletClassNames){
    
    
             Class<?> servletClass = webappClassLoader.loadClass(servletClassName);
             //通过类名,调用webappClassLoard.loadClass()方法,创建Class<?>对象
             servletClassMap.put(servletClassName,servletClass);
         }
    }

    Map<String,Servlet> servletMap = new HashMap<>();
    public void instantiateServletObjects() throws IllegalAccessException, InstantiationException {
    
    
        for(String servletClassName : servletClassMap.keySet()){
    
    
            Servlet servlet = (Servlet) servletClassMap.get(servletClassName).newInstance();
            //通过Class<?>类下的newInstance方法,实例化servlet对象,默认调用该类的无参构造方法
            servletMap.put(servletClassName,servlet);
        }
    }
}

Cookie+セッション設定

セッションとは、Web システムのセッションを指し、ユーザーがログインした後、ログアウトする前のセッションであり、
クライアントが機密性の高いリソースにアクセスするときに、サーバーが ID 認証を実行できるようにする機能です。

Cookie は、実際には小さなテキスト情報です. 原則として、クライアントはユーザーの ID 情報をローカルに保存し、ログイン確認のために HTTP リクエストを送信するたびに Cookie を保持します. シナリオ: ログインを回避して記憶するまでの日数ログイン
ページのパスワード待ち

このプロジェクトは、ユーザーがログインする際のサーバーの検証動作をセッション + クッキー方式で実現します。

具体的なプロセス:
シナリオ: クライアントがユーザー ログイン用のページにアクセスする

  1. クライアントはユーザー名とパスワードを入力し、[送信] をクリックして、サーバーへの検証要求を開始します。
  2. サーバーが初めて要求を受信すると、要求されたユーザー名とパスワードをユーザー オブジェクトにカプセル化し、そのユーザー オブジェクトを Map に格納します。キー = セッション名、値 = ユーザー オブジェクト
  3. サーバー上にローカルにメモリ空間を作成し、その空間にセッション情報を格納する Map を書き込み、UUID を使用してセッション ID をファイル名として生成します。
  4. サーバーはセッション ID を Cookie に保存し、セッション ID を含む Cookie をクライアントに送信します。
  5. 受信後、クライアントはそれをローカルに保存し、リクエストを送信するたびに、サーバーが身元を確認するために sessinID を含む Cookie を送信します。

ここに画像の説明を挿入

//自定义session,用于session相关操作
public class HttpSessionImpl implements HttpSession {
    
    
    private final String sessionID;
    private final Map<String,Object> sessionData;
    private static final String SESSION_BASE = "D:\\JavaDemo\\HttpServlet2.0\\sessions";
	//创建无参的构造方法,没有sessionID传入时使用
    public HttpSessionImpl(){
    
    
        sessionID = UUID.randomUUID().toString();
        sessionData = new HashMap<>();
    }
	//带sessionID的构造方法
    public HttpSessionImpl(String sessionID) throws IOException, ClassNotFoundException {
    
    
        this.sessionID = sessionID;
        sessionData = loadSessionData(sessionID);
    }
	//根据传入的sessionID,判断本地是否存放对应的session信息
    private Map<String, Object> loadSessionData(String sessionID) throws IOException, ClassNotFoundException {
    
    
        String path = SESSION_BASE+"\\"+sessionID+".session";
        File file = new File(path);
        if(!file.exists()){
    
    
            return new HashMap<>();
        }else{
    
    
            try(InputStream inputStream = new FileInputStream(file)){
    
    
                try(ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)){
    
    
                    return (Map<String, Object>) objectInputStream.readObject();
                }
            }
        }
    }
	//获取session数据
    @Override
    public Object getAttribute(String name) {
    
    
        return sessionData.get(name);
    }
	//获取sessionID
    public String getSessionID() {
    
    
        return sessionID;
    }
    public Map<String, Object> getSessionData() {
    
    
        return sessionData;
    }
	//获取存放session的本地地址
    public static String getSessionBase() {
    
    
        return SESSION_BASE;
    }
	//删除session
    @Override
    public void removeAttribute(String name) {
    
    
        sessionData.remove(name);
    }
	//设置session
    @Override
    public void setAttribute(String name, Object value) {
    
    
        sessionData.put(name,value);
    }
	//保存session信息到本地
    public void saveSessionData() throws IOException {
    
    
        String path = SESSION_BASE+"\\"+sessionID+".session";
        try(OutputStream os = new FileOutputStream(path)){
    
    
            try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(os)){
    
    
                objectOutputStream.writeObject(sessionData);
                objectOutputStream.flush();
            }
        }
    }

    @Override
    public String toString() {
    
    
        return "HttpSessionImpl{" +
                "sessionData=" + sessionData +
                '}';
    }
}

認証用のサーブレットを作成する

public class ProfileActionServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, ClassNotFoundException {
    
    
        HttpSession session = req.getSession();
        User user = (User)session.getAttribute("user");
        System.out.println("User:"+user);
        if(user == null){
    
    
            resp.sendRedirect("login.html");
        }else{
    
    
            resp.setContentType("text/plain");
            resp.getWriter().println(user.toString());
        }
    }
}

全体的なプロセス分析

  1. 初期化作業
    1.1 すべてのコンテキストをスキャンする
    1.2 各 Web アプリケーションの構成ファイル web.conf を読み取り、解析する
    1.3 必要な ServletClass を Class<?> としてロードする
    1.4 必要なサーブレット オブジェクトをインスタンス化する
    1.5 init( ) メソッドを実行する

  2. HTTP 要求応答の処理
    2.1 HTTP 要求を読み取り、それを要求オブジェクトに解析します
    2.1.1 要求行 (メソッド、パス、バージョン番号) を解析
    します 2.1.2 要求ヘッダーを解析します (コアは Cookie を解析しています)
    2.1.3 要求本文を解析します
    2.2 ビルド応答オブジェクト
    2.3 URL から解析された contextPath に従って、処理するコンテキストを見つける
    2.4 URL から解析された servletPath に従って、処理するサーブレットを見つける
    2.5 対応する servlet.service() メソッドを呼び出し、
    2.6 応答オブジェクトを送信し、make HTTP レスポンス

ソースコード

github プロジェクトのソース コード

プロジェクトの境界と概要

  • HTTP プロトコル レベルでは、現在 HTTP1.0 バージョンのみ、短い接続のみ、GET メソッドのみがサポートされており、リクエスト メソッドが POST の場合、405 エラーが直接返されます。
  • TCP サーバー処理のために、このプロジェクトは実際の Tomcat NIO モードの代わりに BIO モードを採用しています。
  • サーブレット レベルは、サーブレットのサブセットのみを実装します

HTTP サーバーとして、このプロジェクトは、ネットワークの原理と HTTP プロトコルをより具体的に理解することを目的としています. 完全なプロセスとして、ネットワークの関連知識をより包括的な方法で学び、統合することができます.将来的には、さらに研究し、さらに最適化していきます. 最終的に、このブログが多くのブロガーの役に立てることを非常に光栄に思います.

おすすめ

転載: blog.csdn.net/m0_46233999/article/details/119221547