対応したい内容
servletにmultipart/form-data形式でPOSTされてきたパラメータを読み取るにはHttpServletRequest.getParts()
を用いる方法1や、下記のようにHttpServletRequest.getParameter()
を用いる方法がある。
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を参考にメソッドを追加しています。
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);
}
}
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()
内でラップしても大丈夫。
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
参考
-
Servlet3.0のアップロードで、同時に送られてたパラメータを取得する方法
https://qiita.com/alpha_pz/items/fe808da6d232a14ac7a9 ↩ -
山奥通信 増刊号 ServletInputStreamを2回使う
https://yamaokuno-usausa.hatenablog.com/entry/20090120/1232420325 ↩ ↩2 ↩3 -
multipart/form-dataのパラメータをApache Commons FileUploadで読み取る。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1497572829 ↩ ↩2 -
HttpServletRequestWrapper, example implementation for setReadListener / isFinished / isReady?
https://stackoverflow.com/questions/29208456/httpservletrequestwrapper-example-implementation-for-setreadlistener-isfinish ↩ -
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 ↩