All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.redkale.net.http.MultiContext Maven / Gradle / Ivy

/*
 * To change this license header, choose License Headers input Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template input the editor.
 */
package org.redkale.net.http;

import org.redkale.util.ByteArray;
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
import java.util.regex.*;
import org.redkale.util.AnyValue.DefaultAnyValue;

/**
 * HTTP的文件上传请求的上下文对象
 *
 * 

* 详情见: https://redkale.org * * @author zhangjx */ public final class MultiContext { private static final Logger logger = Logger.getLogger(MultiContext.class.getSimpleName()); private final String contentType; private final InputStream in; private final Charset charset; private final String boundary; private final byte[] endboundarray; private final ByteArray buf = new ByteArray(64); private final DefaultAnyValue parameters; private final Pattern fielnamePattern; private static final Iterable emptyIterable = () -> new Iterator() { @Override public boolean hasNext() { return false; } @Override public MultiPart next() { return null; } }; public MultiContext(final Charset charsetName, final String contentType, final DefaultAnyValue params, final InputStream in, String fielnameRegex) { this.charset = charsetName == null ? StandardCharsets.UTF_8 : charsetName; this.contentType = contentType == null ? "" : contentType.trim(); this.parameters = params; this.boundary = parseBoundary(this.contentType); this.endboundarray = ("--" + this.boundary + "--").getBytes(); this.in = in instanceof BufferedInputStream ? in : new BufferedInputStream(in); this.fielnamePattern = fielnameRegex == null || fielnameRegex.isEmpty() ? null : Pattern.compile(fielnameRegex); } private String parseBoundary(String contentType) { if (!contentType.startsWith("multipart/")) { return null; } for (String str : contentType.split(";")) { int pos = str.indexOf("boundary="); if (pos >= 0) return str.substring(pos + "boundary=".length()).trim(); } return null; } /** * 判断请求是否包含上传文件 * * @return boolean */ public boolean isMultipart() { return this.boundary != null; } //或被 REST 用到 /** * 获取第一个文件的二进制 * * @param max 可接收的文件大小最大值 * @param filenameReg 可接收的文件名正则表达式 * @param contentTypeReg 可接收的ContentType正则表达式 * * @return 二进制文件 * @throws IOException IOException */ public byte[] partsFirstBytes(final long max, final String filenameReg, final String contentTypeReg) throws IOException { if (!isMultipart()) return null; byte[] tmpfile = null; boolean has = false; for (MultiPart part : parts()) { if (has) continue;//不遍历完后面getParameter可能获取不到值 has = true; if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue; if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue; tmpfile = part.getContentBytes(max < 1 ? Long.MAX_VALUE : max); } return tmpfile; } /** * 根据临时文件获取上传时的文件名 * * @param file 临时文件 * * @return 上传的文件名 */ public static String getFileName(File file) { if (file == null) return null; String name = file.getName(); return name.startsWith("redkale-") ? name.substring(name.indexOf('_') + 1) : name; } //或被 REST 用到 /** * 获取第一个文件 * * @param home 进程目录 * @param max 可接收的文件大小最大值 * @param filenameReg 可接收的文件名正则表达式 * @param contentTypeReg 可接收的ContentType正则表达式 * * @return 文件 * @throws IOException IOException */ public File partsFirstFile(final File home, final long max, final String filenameReg, final String contentTypeReg) throws IOException { if (!isMultipart()) return null; File tmpfile = null; boolean has = false; for (MultiPart part : parts()) { if (has) continue; //不遍历完后面getParameter可能获取不到值 has = true; if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue; if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue; File file = new File(home, "tmp/redkale-" + System.nanoTime() + "_" + part.getFilename()); File parent = file.getParentFile(); if (!parent.isDirectory()) parent.mkdirs(); boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file); if (!rs) { file.delete(); parent.delete(); } else { tmpfile = file; } } return tmpfile; } //或被 REST 用到 /** * 获取所有文件 * * @param home 进程目录 * @param max 可接收的文件大小最大值 * @param filenameReg 可接收的文件名正则表达式 * @param contentTypeReg 可接收的ContentType正则表达式 * * @return 文件列表 * @throws IOException IOException */ public File[] partsFiles(final File home, final long max, final String filenameReg, final String contentTypeReg) throws IOException { if (!isMultipart()) return null; List files = null; for (MultiPart part : parts()) { if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue; if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue; File file = new File(home, "tmp/redkale-" + System.nanoTime() + "_" + part.getFilename()); File parent = file.getParentFile(); if (!parent.isDirectory()) parent.mkdirs(); boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file); if (!rs) { file.delete(); parent.delete(); continue; } if (files == null) files = new ArrayList<>(); files.add(file); } return files == null ? null : files.toArray(new File[files.size()]); } /** * 获取上传文件信息列表 * * @return Iterable * @throws IOException IOException */ public Iterable parts() throws IOException { if (!isMultipart()) return emptyIterable; final String boundarystr = "--" + this.boundary; final Pattern fielnameReg = this.fielnamePattern; final String endboundary = boundarystr + "--"; final byte[] boundarray = ("\n" + boundarystr).getBytes(); final byte[] buffer = new byte[boundarray.length]; final InputStream input = this.in; final DefaultAnyValue params = this.parameters; final AtomicBoolean finaled = new AtomicBoolean(false); return () -> new Iterator() { private String boundaryline; private MultiPart lastentry; @Override public boolean hasNext() { try { if (lastentry != null) { lastentry.skip(); if (finaled.get()) return false; } if (boundaryline == null) boundaryline = readBoundary(); //if (debug) System.out.print("boundaryline=" + boundaryline + " "); if (endboundary.equals(boundaryline) || !boundarystr.equals(boundaryline)) { //结尾或异常 lastentry = null; return false; } final String disposition = readLine(); //if (debug) System.out.println("disposition=" + disposition); if (disposition.contains("; filename=\"")) { //是上传文件 String contentType = ""; //读掉HTTP Header和空白行 通常情况下Content-Type后面就是内容,但是有些特殊情况下后面会跟其他如Content-Length: xxx等HTTP header,所以需要循环读取 String rl; while (!(rl = readLine()).isEmpty()) { if (rl.startsWith("Content-Type:")) contentType = rl.substring(rl.indexOf(':') + 1).trim(); } //if (debug) System.out.println("file.contentType=" + contentType); String name = parseValue(disposition, "name"); String filename = parseValue(disposition, "filename"); if (filename == null || filename.isEmpty()) { //没有上传 readLine(); //读掉空白行 this.boundaryline = null; this.lastentry = null; return this.hasNext(); } else { int p1 = filename.lastIndexOf('/'); if (p1 < 0) p1 = filename.lastIndexOf('\\'); if (p1 >= 0) filename = filename.substring(p1 + 1); } final AtomicLong counter = new AtomicLong(0); InputStream source = new InputStream() { private int bufposition = buffer.length; private boolean end; @Override public int read() throws IOException { if (end) return -1; final byte[] buf = buffer; int ch = (this.bufposition < buf.length) ? (buf[this.bufposition++] & 0xff) : input.read(); if ((ch == '\r' && readBuffer())) return -1; counter.incrementAndGet(); return ch; } private boolean readBuffer() throws IOException { final byte[] buf = buffer; final int pos = this.bufposition; int s = 0; for (int i = pos; i < buf.length; i++) { buf[s++] = buf[i]; } int readed = 0; while ((readed += input.read(buf, s + readed, pos - readed)) != pos); this.bufposition = 0; if (Arrays.equals(boundarray, buf)) { this.end = true; int c1 = input.read(); int c2 = input.read(); finaled.set(c1 == '-' && c2 == '-'); return true; } return false; } @Override public long skip(long count) throws IOException { if (end) return -1; if (count <= 0) return 0; long s = 0; while (read() != -1) { s++; if (--count <= 0) break; } return s; } }; this.lastentry = new MultiPart(filename, name, contentType, counter, source); if (fielnameReg != null && !fielnameReg.matcher(filename).matches()) { return this.hasNext(); } return true; } else { //不是文件 readLine(); //读掉空白 params.addValue(parseValue(disposition, "name"), readLine()); this.boundaryline = null; this.lastentry = null; return this.hasNext(); } } catch (IOException ex) { logger.log(Level.FINER, "list multiparts abort", ex); return false; } } @Override public MultiPart next() { return lastentry; } }; } private String readLine() throws IOException { return readLine(false); } private String readBoundary() throws IOException { return readLine(true); } private String readLine(boolean bd) throws IOException { // bd : 是否是读取boundary byte lasted = '\r'; buf.clear(); final int bc = this.endboundarray.length; int c = 0; for (;;) { int b = in.read(); c++; if (b == -1 || (lasted == '\r' && b == '\n')) break; if (lasted != '\r') buf.write(lasted); lasted = (byte) b; if (bd && bc == c) { buf.write(lasted); if (buf.equal(this.endboundarray)) break; buf.removeLastByte(); } } if (buf.size() == 0) return ""; return buf.toString(this.charset).trim(); } private static String parseValue(final String str, String name) { if (str == null) return null; final String key = "; " + name + "=\""; int pos = str.indexOf(key); if (pos < 0) return null; String sub = str.substring(pos + key.length()); return sub.substring(0, sub.indexOf('"')); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy