Java Webベース(3)(HttpServiceletRequest-リクエストベース)

12687 ワード

サーブレットとJSPの関係、サーブレットの基本的な作成と構成、要求/応答中にHTTPサーバ、webコンテナ、サーブレットがどのように連携して動作するかなど、サーブレット/JSPの基礎知識について詳しく説明しました.
Webアプリケーションにとって、要求/応答はその作業工程の基礎であり、ここではHTTPプロトコルに基づく要求/応答モデルのみを考慮し、HttpServertRequestは要求および関連パラメータを表し、HttpServertResponseは応答および関連パラメータを表し、この2つのオブジェクトは1つの要求の開始に伴って確立される.1つの応答が終了するにつれて破棄は回収される.1つの要求が到来すると、HttpServeretは、現在のHTTPの要求方式(GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACEを含む)を判断し、要求方式に対応する方法doXXX()に伝達するHttpSevletRequestおよびHttpServertResponseオブジェクトを作成するサービス()方法を実行する.
HttpSevletRequestについては,要求パラメータの取得,符号化処理,ファイルアップロード受信,要求リダイレクトなどの知識にほかならないが,以下ではよく用いられる知識点を整理してdemoを添付する.
要求パラメータとヘッダーの取得
HttpSevletRequestには、要求パラメータを取得する方法がカプセル化されています.要求パラメータはキー値ペアとして格納されています.次の方法があります.
方法
説明
String getParameter(String key)
要求パラメータ名によるパラメータ値の取得
String[] getParameterValues(String key)
同様に、要求パラメータ名が複数の値に対応する場合がある
Enumeration getParameterNames()
すべてのパラメータ名を取得
Map getParameterMap()
要求パラメータをMapオブジェクトで返す
HTTPのヘッダ情報は、以下の方法で得ることができる.
方法
説明
String getHeader(String key)
類似getParamater()
String[] getHeaders(String key)
getParamaterValues()と同様
Enumeration getHeaderNames()
getParamaterName()と同様
要求パラメータ符号化処理
要求パラメータを取得するには、符号化は必ず考慮しなければならない.そうしないと、乱符号化が発生しやすい.幸い、現在のフレームワークでは、符号化処理の大部分の操作を完了してくれた.どんな場合に文字化けしやすいですか?クライアントが設定した符号化とwebコンテナが使用する符号化が一致しない場合、最も文字化けしやすい.クライアントの符号化は私たち自身が設定したもので、webコンテナの符号化は一般的に1つの要求の中で、Content-Typeのヘッダーの中で設定したもので、例えば“ContentType:text/html;charset=UTF-8”はwebコンテナに教えます:“あなたのwebコンテナは正しいパラメータを取得したいならば、UTF-8で復号しなければなりません.さもなくば問題が発生して私のクライアントがあなたに注意していないことを責めないでください!”HttpServiceletRequestではgetCharacterEncoding()メソッドを使用して現在のリクエストの符号化を取得できます.クライアントがヘッダに符号化情報を設定していない場合、nullが返され、ウェブコンテナはISO-8859-1(ほとんどのブラウザのデフォルトの文字セット)を使用してデフォルトで復号されます.
次に,POSTとGETの2つの最も一般的な要求方式における符号化処理についてのみ議論する.
     POST
クライアントはパラメータを要求にカプセル化します.クライアントの符号化方式がUTF-8の場合、パラメータをカプセル化するときは、次のコードを実行することに相当します.
String parameter = java.net.URLEncoder.encode(value, "UTF-8");

そしてwebコンテナは要求とパラメータをサーブレットに渡し、サーブレットで要求パラメータを取得する場合、事前に符号化を設定していなければ、デフォルトではISO-8859-1を用いて復号され、以下のコードが実行されたことに相当する.
String parameter = java.net.URLEncoder.encode(value, "ISO-8859-1");

符号化と復号化で使用される文字セットが異なるため、文字化けが発生します.
解決策は、クライアントが要求を開始するContent-Typeヘッダに符号化(charset=UTF-8)を設定、サーブレットで要求パラメータを取得する前にrequestを呼び出すことである.setCharacterEncoding():
request.setCharacterEncoding("GBK");

      GET
なぜPOSTとGETは違うのでしょうか?POSTとGETのパラメータ伝達方式が異なるため、HttpServiceletRequestのAPIではserCharacterEncoding()について以下のように説明する.
Overides the name of the character encoding userd in the body of this request

つまりこの方式は要求Bodyにおける文字符号化のみに有用であり、つまりこの方法は基本的にPOSTのみに有用である.GETパラメータはURLによって実現されるが、URLの処理はHTTPサーバによって行われ、Webコンテナではないので、GET方式でパラメータを転送するには、その符号化処理方式が異なる.
また、上記の例では、クライアントの符号化方式はUTF-8であり、以下のコードを実行し、GET方式で要求する.
String parameter = java.net.URLEncoder.encode(value, "UTF-8");

GET方式を使用しているため、webコンテナはどんなコードを設定しても無駄で、デフォルトではISO-8859-1復号を使用しています.
String parameter = java.net.URLEncoder.encode(value, "ISO-8859-1");

サーブレットで復号しなければなりません
String parameter = new String(value.getBytes("ISO-8859-1"), "utf-8");

符号化処理における2つの要求方式の違いを一例として示す.
test-get.html
<!DOCTYPE html>
<html>
<head>
	<meta name="content-type" content="text/html; charset=GBK">
</head>
<body>
	<form action="hello.view" method="get">
		<input type="text" name="username" />
		<button>  GET  </button>
	</form>
</body>
</html>

test-post.html
<!DOCTYPE html>
<html>
<head>
	<meta name="content-type" content="text/html; charset=GBK">
</head>
<body>
	<form action="hello.view" method="post">
		<input type="text" name="username" />
		<button>  POST  </button>
	</form>
</body>
</html>

HelloServlet.java
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String name = request.getParameter("username");
		name = new String(name.getBytes("ISO-8859-1"), "GBK");
		System.out.println("GET:" + name);
	}
	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		request.setCharacterEncoding("GBK");
		String name = request.getParameter("username");
		System.out.println("POST:" + name);
	}

}

実際,サーブレットで直接符号化設定や変換を行うことは最良の方法ではないが,後でフィルタ(Filter)を習得した場合,フィルタで符号化変換を処理し,符号化と論理をデカップリングすることが好ましい.
アップロードファイルの受信
HttpServiceletRequestでは、getReader()メソッド、getInputStream()メソッドがあり、アップロードされたファイルを取得することができますが、これは実現するのが面倒で、ファイルの開始点、ラベル、keyなどを自分で判断する必要があり、エラーも発生しやすいです.そこでサーブレット3.0にPartインタフェースが追加され、ファイルのアップロード処理をより容易に行うことができます.例を挙げます.
upload.html
<!DOCTYPE html>
<html>
<head>
	<meta name="content-type" content="text/html; charset=UTF-8">
</head>
<body>
	<form action="hello.view" method="post" enctype="multipart/form-data">
		    :<input type="file" name="uploadfile" />
		<input type="submit" name="upload" value="  ">
	</form>
</body>
</html>

HelloServlet.java
@MultipartConfig
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		Part part = request.getPart("uploadfile");
		String fileName = getFileName(part);
		save(fileName, part);
	}

	private String getFileName(Part part) {
		String header = part.getHeader("Content-Disposition");
		String fileName = header.substring(header.indexOf("filename=\"") + 10,
				header.lastIndexOf("\""));
		return fileName;
	}
	
	private void save(String fileName, Part part) throws IOException{
		InputStream in = part.getInputStream();
		File file = new File("D:/" + fileName);
		file.mkdirs();
		OutputStream out = new FileOutputStream(file);
		byte[] buffer = new byte[1024];
		int length = -1;
		while ((length = in.read(buffer)) != -1) {
			out.write(buffer, 0, length);
		}
		in.close();
		out.close();
	}
}

クラス名の前に注記@MultipartConfigを付けることを忘れずに、このマークを付けてPartのAPIを使うことができます.また、@MultipartConfigでは、以下の属性を設定できます.
ツールバーの
説明
fileSizeThreshold
アップロードファイルのサイズがここで設定した敷居を超えると、キャッシュファイルに書き込まれます.デフォルトは0です.
location
文字列、ファイルに書き込むディレクトリを設定します.このプロパティを設定すると、キャッシュファイルは指定したディレクトリに書き込まれ、partのwrite()メソッドと組み合わせて使用できます.デフォルトは空の文字列です.
maxFileSize
アップロードファイルのサイズを制限します.デフォルトは-1 Lです.つまり、サイズを制限しません.
maxRequestSize
Multipart/form-dataリクエストの数を表示します.デフォルトは同じです.
上のgetFileName()メソッドでは、ヘッダの切り取りは「filename=」で実現されていますが、なぜここではこのようにハードコーディングされているのでしょうか.私たちはuploadにいるからです.htmlのformにはenctype=「multipart/form-data」が設定されていますが、このenctypeを使用して送信される各コンテンツセグメントには、次のようなスタイルのヘッダー情報があります.
<span style="font-family:Microsoft YaHei;font-size:14px;">Content-Disposition: form-data; name="filename"; filename="xxxx.txt"</span>

以下に、@MultipartConfigのプロパティを設定する例を示します.
@MultipartConfig(location="D:/")
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		Part part = request.getPart("uploadfile");
		String fileName = getFileName(part);
		part.write(fileName);
	}

	private String getFileName(Part part) {
		String header = part.getHeader("Content-Disposition");
		String fileName = header.substring(header.indexOf("filename=\"") + 10,
				header.lastIndexOf("\""));
		return fileName;
	}
}

マルチファイルアップロードの場合、同じformであれば、次の例を参照してください.
<body>
	<form action="hello.view" method="post" enctype="multipart/form-data">
		  1:<input type="file" name="file1" />
		  2:<input type="file" name="file2" />
		  3:<input type="file" name="file3" />
		<input type="submit" name="upload" value="  ">
	</form>
</body>
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		for (Part part: request.getParts()) {
			//       form        ,     part
			if(part.getName().startsWith("file")){
				String fileName = getFileName(part);
				part.write(fileName);
			}
		}
	}

前にwebでできると言いました.注記を使用することなくxmlにサーブレットを登録する方法も、同様に@MultiConfigをwebに置くことができる.xml:
	<servlet>
		<servlet-name>SimpleServlet</servlet-name>
		<servlet-class>com.web.SimpleServlet</servlet-class>
		<multipart-config>
			<location>D:/</location>
		</multipart-config>
	</servlet>

RequestDispatcherを使用したリクエストのディスパッチ
Webアプリケーションでは、1つのリクエストが複数のservletコラボレーションで完了することがしばしば必要であり、複数のservletがリクエストをどのように伝達/引き継ぐか、パラメータをどのように伝達するかなどを考慮する必要があります.HttpServiceletRequestは、getRequestDispatcher()メソッドを使用してRequestDispatcherインタフェースの実装オブジェクトインスタンスを取得します.呼び出し時に呼び出されたservlet名を入力する必要があります.
RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");

RequestDispatcherインタフェースには、次の2つの方法があります.
  • include(サーブレットRequest,サーブレットResponse):リクエスト転送後、元のservletは応答情報を出力し続けることができ、転送を受けたservletのリクエストに対する応答はコード順に元のservlet応答オブジェクトに組み込まれる.
  • forward(サーブレットRequest、サーブレットResponse):応答がクライアントに送信される前に呼び出さなければなりません.そうしないと、例外が放出されます.元のservletは終了し、リクエストにコミットされていないコンテンツは消去され、転送を受けたservletはリクエストに応答する責任を負います.

  • 次に例を示します.
    @WebServlet("/hello.view")
    public class HelloServlet extends HttpServlet {
    
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    			throws ServletException, IOException {
    		PrintWriter out = resp.getWriter();
    		out.println("          ");
    		RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
    		dispatcher.include(req, resp);
    		out.println("   n+1      (n >= 0)");
    		out.close();
    	}
    	
    	public void doPost(HttpServletRequest req, HttpServletResponse resp)
    			throws ServletException, IOException {
    		PrintWriter out = resp.getWriter();
    		out.println("          ");
    		RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
    		dispatcher.forward(req, resp);
    		out.println("          ");
    //		out.close();          
    	}
    }

    要求の派遣時にパラメータを渡す
    クライアントから渡されたパラメータはすべてHttpServeretRequestにカプセル化されているので、次のservletに渡されるに違いありません.最初のservletにいくつかのデータがあり、2番目のservletに渡すには、HttpServeretRequestのsetAttribute()メソッドを使用する必要があります.これは簡単です.次の例です.
    @WebServlet("/hello.view")
    public class HelloServlet extends HttpServlet {
    
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
    			throws ServletException, IOException {
    		PrintWriter out = resp.getWriter();
    		RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
    		List<String> list = new ArrayList<String>();
    		req.setAttribute("userIds", list);
    		dispatcher.include(req, resp);
    		out.close();
    	}
    	
    	public void doPost(HttpServletRequest req, HttpServletResponse resp)
    			throws ServletException, IOException {
    		PrintWriter out = resp.getWriter();
    		RequestDispatcher dispatcher = req.getRequestDispatcher("test.view");
    		List<String> list = new ArrayList<String>();
    		req.setAttribute("userIds", list);
    		dispatcher.forward(req, resp);
    	}
    }

    ここで注意すべき点は、我々が伝達するパラメータは、今回の要求サイクル内でのみ有効であり、要求/応答後にパラメータが破棄されるため、伝達されるパラメータのライフサイクルは、要求/応答のライフサイクルとともに存在/消滅することである.