序文Tomcat の最も完全な UML クラス図 Tomcat リクエスト処理プロセス: Connector オブジェクトが作成されると、Http11NioProtocol の ProtocolHandler が作成されます。Connector の startInteral メソッドでは、AbstractProtocol が開始されます。AbstractProtocol は、クライアントのリクエストをリッスンするために NioEndPoint を開始します。EndPoint はクライアントのリクエストを受信すると、リクエストを処理するために Container に渡します。エンジンから開始してリクエストが通過するすべてのコンテナーには、責任の連鎖パターンが含まれます。コンテナーが通過するたびに、そのコンテナーの責任の連鎖が呼び出され、リクエストが処理されます。 1. エンドポイントデフォルトの EndPoint 実装は NioEndPoint です。NioEndPoint には、Poller、Acceptor、PollerEvent、SocketProcessor、NioSocketWrapper の 4 つの内部クラスがあります。 (1) Acceptor はユーザーリクエストを監視する役割を担います。ユーザーリクエストを監視した後、 (2) Pollerスレッドは、処理可能なイベント(nettyのセレクタ)を巡回し続けます。処理が必要なイベントが見つかると、 (3) PollerEventはRunnableインターフェースを継承します。そのrunメソッドでは、PollerEventのイベントがOP_REGISTERを登録する場合、現在のソケットがPollerセレクタに登録されます。 パブリックボイド実行() { (interestOps == OP_REGISTER)の場合{ 試す { // コアコード、ついに見つかりました! ! ! ! ! // イベントが登録の場合、現在の NioSocketChannel を Poller の Selector に登録します。 socket.getIOChannel().register() を呼び出します。 socket.getPoller().getSelector()、SelectionKey.OP_READ、socketWrapper); } キャッチ (例外 x) { ログエラー(sm.getString("endpoint.nio.registerFail"), x); } } それ以外 { 最終的な選択キーキー = socket.getIOChannel().keyFor(socket.getPoller().getSelector()); 試す { (キー == null)の場合{ // キーがキャンセルされました(ソケットの閉鎖などにより) // セレクタから削除されました // 処理されました。この時点で接続をカウントダウンします // ソケットがオープンされたときにカウントダウンが行われないため // 閉じました。 // SelectionKey がキャンセルされた場合、SelectionKey に対応する EndPoint の接続カウンターを 1 つ減らす必要があります。socket.socketWrapper.getEndpoint().countDownConnection(); ((NioSocketWrapper) socket.socketWrapper).closed = true; } それ以外 { 最終的な NioSocketWrapper socketWrapper = (NioSocketWrapper) key.attachment(); ソケットラッパーが null の場合 // 最初にキーを登録し、公平性カウンターをリセットします。 int ops = key.interestOps() | interestOps; socketWrapper.interestOps(ops); キー.interestOps(ops); } それ以外 { socket.getPoller().cancelledKey(キー); } } } キャッチ (CancelledKeyException ckx) { 試す { socket.getPoller().cancelledKey(キー); } catch (例外を無視) { } } } } (4) ポーラースレッドは、 // selectedKeys の反復子を取得します Iterator<SelectionKey> iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null; // すべての SelectionKey を走査して処理します while (iterator != null && iterator.hasNext()) { 選択キー sk = iterator.next(); イテレータを削除します。 NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment(); // 別のスレッドが呼び出した場合、添付ファイルはnullになることがあります // キャンセルされたキー() // 添付ファイルがある場合はそれを処理する if (socketWrapper != null) { //イベント処理 processKey(sk, socketWrapper); } } processKey は SelectionKey を処理しています。現在の Poller が閉じられている場合、キーはキャンセルされます。 SelectionKey に対応する Channel で読み取りイベントが発生すると、 AbatractEndPoint.processSocket が呼び出され、読み取り操作 保護されたvoid processKey(SelectionKey sk、NioSocketWrapperアタッチメント) { 試す { (閉じる){ // ポーラーが閉じている場合はキーをキャンセルします キャンセルされたキー(sk); } そうでない場合 (sk.isValid() && attachment != null) { sk.isReadable() の場合 || sk.isWritable() の場合 { (attachment.getSendfileData() != null) の場合 { processSendfile(sk、添付ファイル、false); } それ以外 { 登録を解除します(sk、添付ファイル、sk.readyOps()); ブール値 closeSocket = false; // 書き込みの前に読み取りを行う // 読み取りは書き込みよりも優れています // SelectionKey に対応するチャネルが読み取り準備ができている場合は、 // NioSocketWrapper を読み取ります if (sk.isReadable()) { 添付ファイルを SocketEvent.OPEN_READ で処理すると true になります。 ソケットを閉じる = true; } } // SelectionKeyに対応するチャネルが書き込み可能な場合 // NioSocketWrapperに書き込みます if (!closeSocket && sk.isWritable()) { 添付ファイルをprocessSocketで処理する場合、SocketEvent.OPEN_WRITEがtrueであるかどうかを確認します。 ソケットを閉じる = true; } } ソケットを閉じる場合 // すでに閉じられている場合はキーをキャンセルします キャンセルされたキー(sk); } } } } AbatractEndPoint.processSocket メソッドは、まずキャッシュから SocketProcessor クラスを取得します。キャッシュに SocketProcessor がない場合は、作成します。SocketProcessorBase インターフェイスは、Worker である NioEndPoint.SocketProcessor に対応します。対応する SocketProcessor クラスをスレッド プールに入れて実行します。 パブリックブール型プロセスソケット(SocketWrapperBase<S> socketWrapper, SocketEvent イベント、ブールディスパッチ){ // ソケット プロセッサを取得します // コネクタはコンストラクターでプロトコルを指定しています: org.apache.coyote.http11.Http11NioProtocol。 SocketProcessorBase<S> sc = プロセッサキャッシュ.pop(); (sc == null)の場合{ // そうでない場合は、ソケット ハンドラーを作成します。作成時に socketWrapper とソケット イベントを指定します。 sc = createSocketProcessor(socketWrapper、イベント); } それ以外 { sc.reset(socketWrapper、イベント); } //ソケット処理はスレッド プールに引き渡されます。 実行者 executor = getExecutor(); if (ディスパッチ && エグゼキュータ != null) { 実行者.execute(sc); } それ以外 { sc.run(); } (5) NioEndPoint.NioSocketWrapperはSocketのカプセル化クラスおよび拡張クラスであり、Socketを他のオブジェクトに関連付けます。 パブリック静的クラス NioSocketWrapper は SocketWrapperBase<NioChannel> を拡張します { プライベート最終 NioSelectorPool プール; private Poller poller = null; // ポーリング Poller プライベート int interestOps = 0; プライベートCountDownLatch readLatch = null; プライベートCountDownLatch writeLatch = null; プライベート揮発性 SendfileData sendfileData = null; プライベート volatile long lastRead = System.currentTimeMillis(); プライベート volatile long lastWrite = lastRead; プライベート volatile ブール値 closed = false; (6) NioEndPoint.SocketProcessor (Worker)はRunnableインターフェースを継承し、ソケットのさまざまなイベントの処理を担当します。読み取りイベント、書き込みイベント、停止時間、タイムアウト イベント、切断イベント、エラー時間、接続失敗イベント。 SocketProcessor の doRun メソッドは、SocketState に従って処理を行います。SocketState が STOP、DISCONNECT、または ERROR の場合は閉じられます。SocketWrapperBase に対応するセレクター イベントは、指定された Handler プロセッサによって処理されます。 @オーバーライド 保護されたvoiddoRun() { NioChannel ソケット = socketWrapper.getSocket(); 選択キーキー = socket.getIOChannel().keyFor(socket.getPoller().getSelector()); 試す { int ハンドシェイク = -1; 試す { (キー != null) の場合 { ソケットがハンドシェイクを完了している場合 // ハンドシェイクが成功し、TLS (暗号化) ハンドシェイクが必要ない場合は、プロセッサにソケットとイベントの組み合わせを処理させます。 ハンドシェイク = 0; } そうでない場合、(イベント == SocketEvent.STOP || イベント == SocketEvent.DISCONNECT || イベント == SocketEvent.ERROR) { // TLS ハンドシェイクが完了できない場合は、TLS ハンドシェイクの失敗と見なされます。 ハンドシェイク = -1; } それ以外 { ハンドシェイク = socket.handshake(key.isReadable(), key.isWritable()); // ハンドシェイクプロセスは、 // ソケット。ステータスはOPEN_WRITEになる可能性がある // ハンドシェイクが完了します。ただし、ハンドシェイク // ソケットが開かれたときに発生し、ステータスは // 完了後は常に OPEN_READ である必要があります。 // これは常に設定しても問題ありません。 // ハンドシェイクが完了します。 // ソケットからの読み取り/ソケットへの書き込みのハンドシェイクを行う場合、ハンドシェイクが完了するとステータスは OPEN_WRITE になります。 // ハンドシェイクはソケットが開かれたときに行われるため、完了後は状態は常に OPEN_READ である必要があります。 // このオプションはハンドシェイクが完了したときにのみ使用されるため、常に設定しても問題ありません。 イベント = SocketEvent.OPEN_READ; } } } キャッチ (IOException x) { ハンドシェイク = -1; if (log.isDebugEnabled()) log.debug("SSL ハンドシェイク中にエラーが発生しました", x); } キャッチ (CancelledKeyException ckx) { ハンドシェイク = -1; } ハンドシェイクが 0 の場合 ソケット状態 = SocketState.OPEN; // このソケットからのリクエストを処理する if (イベント == null) { // 処理のためにハンドラを呼び出します。 // NioEndPoint のデフォルトのハンドラーは Http11 です // ここでのハンドラーは AbstractProtocol.ConnectionHandler です // このハンドラの設定方法は次のとおりです: // まず、Connector クラスのコンストラクタで、デフォルトの ProtocolHandler を org.apache.coyote.http11.Http11NioProtocol に設定します。 // ハンドラクラスConnectionHandlerはAbstractHttp11Protocolのコンストラクタで作成されます 状態 = getHandler().process(socketWrapper、SocketEvent.OPEN_READ); } それ以外 { 状態 = getHandler().process(socketWrapper、イベント); } // 返された状態がSocketStateの場合、接続を閉じます。if (state == SocketState.CLOSED) { close(ソケット、キー); } } そうでない場合 (ハンドシェイク == -1) { getHandler() プロセス (socketWrapper、SocketEvent.CONNECT_FAIL); close(ソケット、キー); } そうでない場合 (ハンドシェイク == SelectionKey.OP_READ) { // SelectionKey.OP_READ、つまり読み取りイベントの場合は、OP_READ時間をsocketWrapperに設定します socketWrapper.registerReadInterest(); } そうでない場合 (ハンドシェイク == SelectionKey.OP_WRITE) { // SelectionKey.OP_WRITE、つまり読み取りイベントの場合は、OP_WRITEイベントをsocketWrapperに設定します socketWrapper.registerWriteInterest(); } 2. 接続ハンドラ(1)ConnectionHandlerは、ソケット接続に基づいて対応するエンジンプロセッサを見つけるために使用されます。 上記は 保護された静的クラスConnectionHandler<S>はAbstractEndpoint.Handler<S>を実装します。 プライベート最終AbstractProtocol<S> proto; プライベート最終RequestGroupInfoグローバル = 新しいRequestGroupInfo(); プライベート最終AtomicLongレジスタカウント = 新しいAtomicLong(0); // 最終的にこのコレクションが見つかり、ソケットとプロセッサ間の接続が確立されました。 // 有効な各リンクはここでキャッシュされ、接続されて、要求処理に適したプロセッサ実装が選択されます。 プライベート最終 Map<S, Processor> 接続 = 新しい ConcurrentHashMap<>(); // リサイクル可能なプロセッサ スタック private final RecycledProcessors recycledProcessors = new RecycledProcessors(this); @オーバーライド パブリックSocketStateプロセス(SocketWrapperBase<S>ラッパー、SocketEventステータス) { getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.process", wrapper.getSocket(), ステータス)); } if (ラッパー == null) { // wrapper == null はソケットが閉じられていることを意味するため、アクションは必要ありません。 SocketState.CLOSED を返します。 } // ラッパー内のソケットオブジェクト S を取得します。socket = wrapper.getSocket(); // マップ バッファーからソケットに対応するプロセッサを取得します。 プロセッサ プロセッサ = connections.get(socket); getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", プロセッサ、ソケット)); } // タイムアウトは専用スレッドで計算され、その後 // ディスパッチされました。ディスパッチ処理の遅延により、 // タイムアウトは不要になる場合があります。ここをチェックして回避してください // 不要な処理。 // タイムアウトは専用スレッドで計算され、スケジュールされます。 // スケジュール プロセスの遅延により、タイムアウトは必要なくなる可能性があります。不要な処理を避けるためにここにチェックを入れてください。 if (SocketEvent.TIMEOUT == ステータス && (プロセッサ == null || !processor.isAsync() && !processor.isUpgrade() || プロセッサ.isAsync() && !プロセッサ.checkAsyncTimeoutGeneration())) { // これは実質的にはNO-OPです SocketState.OPEN を返します。 } // マップキャッシュにソケットに関連付けられたプロセッサがある場合 if (processor != null) { // 非同期タイムアウトが発生しないようにする // 非同期タイムアウトがトリガーされないことを確認します getProtocol().removeWaitingProcessor(processor); } そうでない場合 (ステータス == SocketEvent.DISCONNECT || ステータス == SocketEvent.ERROR) { // 何もしません。エンドポイントがクローズを要求しましたが、 // このソケットに関連付けられたプロセッサはなくなりました。 // SocketEvent イベントが閉じられているか、SocketEvent の時間が間違っているため、現時点では操作は必要ありません。 // エンドポイントには CLOSED シグナルが必要ですが、このソケットに関連付けられた接続はもうありません。return SocketState.CLOSED; } コンテナスレッドマーカーを設定します。 試す { // マップキャッシュにはこのソケットに関連付けられたプロセッサが含まれていません if (processor == null) { 文字列のネゴシエートされたプロトコル = wrapper.getNegotiatedProtocol(); // OpenSSLは通常nullを返しますが、JSSEは通常 // プロトコルがネゴシエートされていない場合は "" を返します // OpenSSL は通常 null を返しますが、JSSE は通常、プロトコルがネゴシエートされていない場合は "" を返します。 ネゴシエートされたプロトコルが null で、ネゴシエートされたプロトコルの長さが 0 より大きい場合、 // ネゴシエーション プロトコルを取得します。UpgradeProtocol upgradeProtocol = getProtocol().getNegotiatedProtocol(negotiatedProtocol); アップグレードプロトコルが null の場合 // アップグレード プロトコルは空です。processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter()); getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.processorCreate", プロセッサ)); } } そうでない場合 (ネゴシエートされたプロトコルが "http/1.1" の場合) { // デフォルトのプロトコルを明示的にネゴシエートしました。 // 以下のプロセッサを取得します。 } それ以外 { // やるべきこと: // OpenSSL 1.0.2のALPNコールバックはサポートしていません // ハンドシェイクに失敗し、エラーが発生する // プロトコルはネゴシエートできます。したがって、 // ここで接続に失敗します。これが修正されると、 // 以下のコードをコメントアウトされたコードに置き換えます // ブロック。 getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.negotiatedProcessor.fail", ネゴシエートされたプロトコル)); } SocketState.CLOSED を返します。 /* * OpenSSL 1.1.0がリリースされたら、上記のコードを置き換えるには * 使用済み。 // プロセッサの作成に失敗しました。これはバグです。 新しいIllegalStateExceptionをスローします(sm.getString( "abstractConnectionHandler.negotiatedProcessor.fail", ネゴシエートされたプロトコル)); */ } } } // 上記の操作後、プロセッサはまだ null です。 if (プロセッサ == null) { // リサイクル可能なプロセッサ群recycledProcessorsからプロセッサを取得します プロセッサ = リサイクルプロセッサ.pop(); getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.processorPop", プロセッサ)); } } if (プロセッサ == null) { // プロセッサを作成するprocessor = getProtocol().createProcessor(); レジスタ(プロセッサ); getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.processorCreate", プロセッサ)); } } プロセッサ.setSslSupport() wrapper.getSslSupport(getProtocol().getClientCertProvider())); // ソケットをプロセッサに関連付けます。 接続.put(ソケット、プロセッサ); SocketState 状態 = SocketState.CLOSED; する { // プロセッサのプロセスメソッドを呼び出します。 状態 = プロセッサ.プロセス(ラッパー、ステータス); // プロセッサのプロセスメソッドはアップグレードステータスを返します if (state == SocketState.UPGRADING) { // HTTPアップグレードハンドラを取得する // HTTP アップグレード ハンドルを取得します。UpgradeToken upgradeToken = processer.getUpgradeToken(); // 残った入力を取得する // 残りの入力を取得します ByteBuffer leftOverInput = processer.getLeftoverInput(); アップグレードトークンが null の場合 // 直接 HTTP/2 接続を想定 アップグレードプロトコル アップグレードプロトコル = getProtocol().getUpgradeProtocol("h2c"); アップグレードプロトコルが null の場合 // 再利用できるように Http11 プロセッサを解放する リリース(プロセッサ); // アップグレードプロセッサを作成する プロセッサ = アップグレードプロトコル.getProcessor(ラッパー、getProtocol().getAdapter()); wrapper.unRead(左入力オーバー); // 接続でプロセッサを関連付ける 接続.put(ソケット、プロセッサ); } それ以外 { getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString( "abstractConnectionHandler.negotiatedProcessor.fail", "h2c"); } // ループを終了し、適切なクリーンアップをトリガーします 状態 = SocketState.CLOSED; } } それ以外 { アップグレードトークンを取得します。 // 再利用できるように Http11 プロセッサを解放する リリース(プロセッサ); // アップグレードプロセッサを作成する プロセッサ = getProtocol().createUpgradeProcessor(ラッパー、upgradeToken); getLog().isDebugEnabled() の場合 { getLog().debug(sm.getString("abstractConnectionHandler.upgradeCreate", プロセッサ、ラッパー)); } wrapper.unRead(左入力オーバー); // 接続でプロセッサを関連付ける 接続.put(ソケット、プロセッサ); // アップグレードハンドラを初期化します(これにより、 // 新しいプロトコルを使用するIOがあるため、 // 上記は必須です) // このキャストは安全です。失敗した場合はエラーが発生します // 周囲のtry/catchの処理は、 // それ。 (アップグレードトークンのgetInstanceManager()がnullの場合){ httpUpgradeHandler.init((WebConnection) プロセッサ); } それ以外 { クラスローダー oldCL = upgradeToken.getContextBind().bind(false, null); 試す { httpUpgradeHandler.init((WebConnection) プロセッサ); ついに アップグレードトークン.getContextBind().unbind(false, oldCL); } } } } } while (状態 == SocketState.UPGRADING); (2) Http11プロトコルを例にとると、Http11Processorが実行されます。Http11Processorの親クラスであるAbstractProcessorLightは、プロセスメソッドを実装しています。プロセスは、Http11Processorによって実装されたサービステンプレートメソッドを呼び出します。サービス メソッドの最も重要な操作は @オーバーライド パブリック SocketState サービス (SocketWrapperBase<?> socketWrapper) IOException をスローします { // 上記では n 行を省略 // Coyote のサービス メソッドを呼び出します getAdapter().service(request, response); // 次のn行は省略されます 3. コヨーテCoyoteAdapter は Connector の initInternal メソッドで作成されることを思い出してください。 @オーバーライド パブリック SocketState サービス (SocketWrapperBase<?> socketWrapper) IOException をスローします { // 上記では n 行を省略 // Coyote のサービス メソッドを呼び出します getAdapter().service(request, response); // 次のn行は省略されます Coyote の機能は、coyote.Request と coyote.Rsponse を HttpServletRequest と HttpServletRsponse に変換することです。次に、コネクタは初期化中に CoyoteAdapter に自身を挿入するため、 @オーバーライド パブリック SocketState サービス (SocketWrapperBase<?> socketWrapper) IOException をスローします { // 上記では n 行を省略 // Coyote のサービス メソッドを呼び出します getAdapter().service(request, response); // 次のn行は省略されます 4. コンテナ責任チェーンパターン次は、StandradEngine から始まる責任連鎖モデルです。まず、StandradEngine の責任チェーン モードを実行して適切なエンジンを見つけます。次に、適切なエンジンは、StandardWrapperValve が見つかるまで、責任チェーン モードを通じて適切なコンテキストを見つけます。最後に、StandardWrapperValve のinvoke メソッドが実行されます。まず、Context と Wrapper が使用できないかどうかを確認します。これらが使用可能で、Servelt が初期化されていない場合は、初期化操作を実行します。シングルスレッドモードの場合は、以前に作成した Servelt を直接返します。マルチスレッドモードの場合は、最初に Servelt オブジェクトを作成して返します。 @オーバーライド public final void invoke(Request リクエスト、Response レスポンス) IOException、ServletException をスローします { // 必要なローカル変数を初期化します boolean unavailable = false; スロー可能 throwable = null; // これはリクエスト属性である必要があります... 長いt1 = System.currentTimeMillis(); // アトミック クラス AtomicInteger、CAS 操作、リクエストの数を示します。 リクエストカウントを増加して取得します。 StandardWrapper ラッパー = (StandardWrapper) getContainer(); サーブレット servlet = null; コンテキスト context = (コンテキスト) wrapper.getParent(); // 現在のコンテキストアプリケーションが利用不可としてマークされているかどうかを確認します if (!context.getState().isAvailable()) { // 現在のアプリケーションが利用できない場合は、503 エラーを報告します。 レスポンス.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable")); 利用不可 = true; } // Servelt が利用不可としてマークされているかどうかを確認します if (!unavailable && wrapper.isUnavailable()) { container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName())); 使用可能な長さ = wrapper.getAvailable(); if ((利用可能 > 0L) && (利用可能 < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", 利用可能); レスポンス.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } そうでない場合 (利用可能 == Long.MAX_VALUE) { レスポンス.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } 利用不可 = true; } // Servelt は最初に呼び出されたときに初期化されます try { 利用できない場合 // この時点で Servelt が初期化されていない場合は、リクエストを処理するために Servelt インスタンスを割り当てます。 サーブレット = wrapper.allocate(); } /// コードを省略................................................ // // リクエストのフィルター チェーンを作成します。フィルタチェーンが実行された後、Servelt アプリケーションフィルタチェーン フィルタチェーン = ApplicationFilterFactory.createFilterChain(リクエスト、ラッパー、サーブレット); // このリクエストのフィルタチェーンを呼び出す // 注意: これはサーブレットの service() メソッドも呼び出します 試す { if ((サーブレット != null) && (フィルターチェーン != null)) { // 必要に応じて出力を飲み込む コンテキスト.getSwallowOutput() の場合 { 試す { SystemLogHandler.startCapture(); リクエストが非同期にディスパッチされるかどうか リクエスト。getAsyncContextInternal()。doInternalDispatch(); } それ以外 { //フィルターチェーンを呼び出す filterChain.doFilter(request.getRequest(), レスポンスを取得します。 } /// コードを省略................................................ Tomcat ソースコード解析と Web リクエストおよび処理に関するこの記事はこれで終わりです。Tomcat の Web リクエストおよび処理に関するその他のコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: 1つの記事でJavaScriptのクロージャ関数について学ぶ
>>: Webデザインチュートリアル(7):Webデザインの効率化
長い引用には blockquote を、短い引用には q を、参考文献には cite を使用します。...
<br />「XXXのウェブサイトを見てみませんか?」といった質問をされることもあります...
目次Redux Toolkitが解決する問題何が含まれていますか? Redux Toolkit AP...
この記事は主にMySQLデータ移行方法とツールの分析を紹介します。サンプルコードを通じて詳細に紹介さ...
序文最近、偶然 MySQL の coalesce を発見しました。ちょうど時間があったので、MySQ...
1. 入力・貼り付けできるのは中国語のみ<input onkeyup="value=...
メタ属性には、name と http-equiv の 2 つがあります。 name 属性は主に、We...
ページ上の画像を強調表示することは非常に一般的です。ここでは、jQuery を使用して画像を強調表示...
この記事では、トップに戻るボタンを実装するためのJavaScriptの具体的なコードを参考までに紹介...
目次遅延読み込みCSS スタイル: HTML部分:スクリプト部分:要約する遅延読み込み名前の通り、私...
目次グローバル変数として可変ホイスト一時的なデッドゾーンブロックスコープ重複したステートメント宣言さ...
概要ボリュームは、さまざまなストレージ リソースを抽象化および仮想化したものです。ストレージ リソー...
プロジェクトでは、フォーム テストが頻繁に発生します。単一のフォーム テストについては、詳細な紹介が...
MySQL を使用する場合、日付は通常、datetime や timestamp などの形式で保存さ...
目次ウェブAPI DOM DOMツリーDOM要素取得方法ドキュメントオブジェクトのプロパティイベント...