1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

HttpServletでInputStreamを複数回読めるようにしつつmultipart/form-dataのパラメータを取得する

Last updated at Posted at 2021-02-22

対応したい内容

servletにmultipart/form-data形式でPOSTされてきたパラメータを読み取るにはHttpServletRequest.getParts()を用いる方法1や、下記のようにHttpServletRequest.getParameter()を用いる方法がある。

TestServlet.java
package test;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
@WebServlet(name="Test Servlet", urlPatterns={"/*"})
@MultipartConfig()
public class TestServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
		try {
			// multipart/form-data のパラメータを取得する
			for (Enumeration<String> parameters = req.getParameterNames(); parameters.hasMoreElements();) {
				String parameter = parameters.nextElement();
				System.out.println("parameter:" + parameter + ", value:" + req.getParameter(parameter));
			}
			// inputStreamをresponse bodyに出力
			// しかし既にinputStreamはgetParameter()により消費されてしまっているのでbodyには何も書き込まれない。
			req.getInputStream().transferTo(resp.getOutputStream());
		} catch (IOException e) {
			System.out.println(e.getStackTrace());
		}
	}
}

しかしgetParts()getParameter()を使うとHttpServletRequest.getInputStream()を消費してしまうので再び読み出すことはできなくなる。inputStreamは別の用事で使いたい場合もあるのでこれを何とかしたい。

対策

こちらの記事2を参考にgetInputStream()の内容をbyte配列にキャッシュし、そこから毎回inputStreamを生成するようにする。ただこうするとgetParts()getParameter()でmultipart/form-dataのパラメータを取得できなくなるので、こちらはApache Commons FileUpload3で読み取るようにする。

実装例

基本的には参考リンクのコード23のコピペですが、少なくとも後述の検証環境ではisFinished(), isReady(), setReadListenerのオーバーライドも必要になったためstuck over flowの記事45を参考にメソッドを追加しています。

BufferedServletRequestWrapper.java
package test;

import java.io.IOException;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class BufferedServletRequestWrapper extends HttpServletRequestWrapper {

	private byte[] buffer;

	public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
		super( request );
		buffer = request.getInputStream().readAllBytes();
	}

	@Override
	public ServletInputStream getInputStream() {
		return new BufferedServletInputStream(buffer);
	}
}
BufferedServletInputStream.java
package test;

import java.io.ByteArrayInputStream;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;

public class BufferedServletInputStream extends ServletInputStream {

	private ByteArrayInputStream inputStream;

	public BufferedServletInputStream(byte[] buffer) {
		this.inputStream = new ByteArrayInputStream( buffer );
	}

	@Override
	public int available() {
		return inputStream.available();
	}

	@Override
	public int read() {
		return inputStream.read();
	}

	@Override
	public int read(byte[] b, int off, int len) {
		return inputStream.read( b, off, len );
	}

	@Override
	public boolean isFinished() {
		return available() == 0;
	}

	@Override
	public boolean isReady() {
		return true;
	}

	@Override
	public void setReadListener(ReadListener listener) {
		throw new RuntimeException("Not implemented");
	}
}

使用例

下記ではdoPost()内で元のリクエストをBufferedServletRequestWrapperでラップしていますが、元記事2のようにdoFilter()内でラップしても大丈夫。

TestServlet.java
package test;

import java.util.List;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@SuppressWarnings("serial")
@WebServlet(name="Test Servlet", urlPatterns={"/*"})
public class TestServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
		try {
			// requestのinputStreamを複数回使えるようにする。
			req = new BufferedServletRequestWrapper(req);

			// multipart/form-dataのパラメータを読み取る。
			FileItemFactory factory = new DiskFileItemFactory();
			ServletFileUpload upload = new ServletFileUpload( factory );
			List<FileItem> uploadItems = upload.parseRequest(req);
			for( FileItem uploadItem : uploadItems ) {
				if(	true
					// && uploadItem.isFormField()		// パラメータのuploadFileを除外したいならコメントアウトを外してみて
						) {
					String fieldName = uploadItem.getFieldName();
					String value = uploadItem.getString();
					System.out.println("fieldName:" + fieldName + " value:" + value);
				}
			}

			// inputStreamをbodyに出力
			// inputStream 2回目の使用だが読み込める・・・!
			req.getInputStream().transferTo(resp.getOutputStream());
		} catch (Exception e) {
			System.out.println(e.getStackTrace());
		}
	}
}

検証環境

  • Java 11
  • tomcat 8.5.59
  • javax.servlet-api 3.1.0

参考

  1. Servlet3.0のアップロードで、同時に送られてたパラメータを取得する方法
    https://qiita.com/alpha_pz/items/fe808da6d232a14ac7a9

  2. 山奥通信 増刊号 ServletInputStreamを2回使う
    https://yamaokuno-usausa.hatenablog.com/entry/20090120/1232420325 2 3

  3. multipart/form-dataのパラメータをApache Commons FileUploadで読み取る。
    https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1497572829 2

  4. HttpServletRequestWrapper, example implementation for setReadListener / isFinished / isReady?
    https://stackoverflow.com/questions/29208456/httpservletrequestwrapper-example-implementation-for-setreadlistener-isfinish

  5. Http Servlet request lose params from POST body after read it once
    https://stackoverflow.com/questions/10210645/http-servlet-request-lose-params-from-post-body-after-read-it-once/17129256

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?