getReader()、getInputStream()



HttpServletRequest 上定義有 getReader() 方法,可以讓你取得一個 BufferedReader,透過該 BufferedReader,可以讓你讀取請求的本體(Body)資料。例如:

package cc.openhome;

import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/postbody")
public class PostBody extends HttpServlet {
    protected void doPost(HttpServletRequest request, 
            HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("<!DOCTYPE html>");
        out.println("<html>");
        out.println("<body>");
        out.println(bodyContent(request.getReader()));
        out.println("</body>");
        out.println("</html>");
    }

    private String bodyContent(BufferedReader reader) throws IOException {
        String input = null;
        StringBuilder requestBody = new StringBuilder();
        while((input = reader.readLine()) != null) {
            requestBody.append(input)
                       .append("<br>");
        }
        return requestBody.toString();
    } 
}

試著對這個 Servlet 以下列表單發出請求:

<!DOCTYPE html>
<html>
<body>
    <form action="postbody" method="post">
        Username: <input type="text" name="user"><br> 
        Password: <input type="password" name="passwd"><br> 
        <input type="submit" name="login">
    </form>
</body>
</html>

則你在網頁上會看到以下內容,因為 POST 時,查詢字串(Query string)是放在請求本體之中發送:

user=caterpillar&passwd=123456&login=%B0e%A5X%ACd%B8%DF

HTML 的 <form> 標籤,enctype 屬性預設值是 "application/x-www-form-urlencoded",如果上傳的是檔案,必須設定為 "multipart/form-data",原理可以參考〈HTTP 檔案上傳機制解析〉的內容。如果你使用以下的表單選擇一個檔案發送:

<!DOCTYPE html>
<html>
<body>
    <form action="postbody" method="post"
          enctype="multipart/form-data">
          Select File :<input type="file" name="filename" value="" /><br>
        <input type="submit" value="Upload" name="upload" />
    </form>
</body>
</html>

例如發送一個 JPG 圖檔,則在網頁上會看到:

-----------------------------7e11a63520166
Content-Disposition: form-data; name="filename"; filename="caterpillar.jpg"
Content-Type: image/pjpeg

會有一堆奇奇怪怪的字元,這些字元是實際的檔案內容

-----------------------------7e11a63520166
Content-Disposition: form-data; name="upload"

Upload
-----------------------------7e11a63520166--

所以你要取得上傳的檔案,就是判斷檔案的開始與結束區段,你可以使用 HttpServletRequestgetInputStream() 取得 ServletInputStream,它是 InputStream 的子類別,代表請求本體的串流物件,你可以利用它來處理上傳的檔案區段。例如:

package cc.openhome;

import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

@WebServlet("/upload")
public class Upload extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        byte[] content = bodyContent(request);
        String contentAsTxt = new String(content, "ISO-8859-1");

        String filename = filename(contentAsTxt);
        Range fileRange = fileRange(contentAsTxt, request.getContentType());

        write(
            content, 
            contentAsTxt.substring(0, fileRange.start)
                        .getBytes("ISO-8859-1")
                        .length, 
            contentAsTxt.substring(0, fileRange.end)
                        .getBytes("ISO-8859-1")
                        .length, 
            "c:/workspace/" + filename
        );
    }

    private byte[] bodyContent(HttpServletRequest request) throws IOException {
        try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            InputStream in = request.getInputStream();
            byte[] buffer = new byte[1024];
            int length = -1;
            while((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        }
    }

    private String filename(String contentTxt) throws UnsupportedEncodingException {
        Pattern pattern = Pattern.compile("filename=\"(.*)\"");
        Matcher matcher = pattern.matcher(contentTxt);
        matcher.find();
        return matcher.group(1);
    }

    private static class Range {
        final int start;
        final int end;
        public Range(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    private Range fileRange(String content, String contentType) {
        Pattern pattern = Pattern.compile("filename=\".*\"\\r\\n.*\\r\\n\\r\\n(.*+)");
        Matcher matcher = pattern.matcher(content);
        matcher.find();
        int start = matcher.start(1);

        String boundary = contentType.substring(
                contentType.lastIndexOf("=") + 1, contentType.length());
        int end = content.indexOf(boundary, start) - 4;   

        return new Range(start, end);
    }

    private void write(byte[] content, int start, int end, String file) throws IOException {
        try(FileOutputStream fileOutputStream = new FileOutputStream(file)) {
            fileOutputStream.write(content, start, (end - start));
        }   
    }
}

你可以利用這個表單來選擇檔案上傳:

<!DOCTYPE html>
<html>
<body>
    <form action="upload" method="post"
          enctype="multipart/form-data">
          Select File :<input type="file" name="filename" value="" /><br>
        <input type="submit" value="Upload" name="upload" />
    </form>
</body>
</html>

在同一個請求期間,getReader()getInputStream() 只能擇一呼叫,若同一請求期間兩者都有呼叫,則會丟出 IllegalStateException 例外。