zoukankan      html  css  js  c++  java
  • Java使用HTTP编程模拟多参数多文件表单信息的请求与处理

    本文目的是提供Java环境下模拟浏览器页面提交多参数多文件表单请求以及解析请求的Demo代码。这里用Java提供的HttpURLConnection类做HTTP请求,再原始点可以直接使用socket。使用socket的话,通用性会更好点。

    首先要了解一个概念,文件和参数一起上传,HTTP请求头中包含了如下格式。

    Content-Type: multipart/form-data; boundary=boundarystr

    请求体的数据格式也是有一个标准的,具体如下。

    HTTP请求头
    
    --boundarystr
    Content-Disposition: form-data; name="param1"
    
    param1value
    --boundarystr
    Content-Disposition: form-data; name="param2"
    
    param2value
    --boundarystr
    Content-Disposition: form-data; name="param3"; filename="filename1"
    Content-Type: application/octet-stream
    
    文件数据
    --boundarystr
    Content-Disposition: form-data; name="param4"; filename="filename2"
    Content-Type: application/octet-stream
    
    文件数据
    --boundarystr--

    boundary参数一般会取一些类似于UUID这样的值。请求头和请求体之间隔了两个回车,也就是一个" "。双横杠boundary和Content-Disposistion描述之间有一个回车(" ")。name和filename的参数值需要加双引号。Content描述和数据之间有两个回车(" ")。请求提最后的boundary后尾需要跟上双横杠。例子中,用application/octet-stream通用的描述文件数据内容格式,如果事先知道文件格式,可以根据MIME类型(http://www.w3school.com.cn/media/media_mimeref.asp)填写。好处是解析时候比较方便。

    在编写代码时必须严格遵循这个消息格式,包括换行和横杠以及boundary。否则会导致数据无法解析。

    下面是一个制作该HTTP消息并进行请求的代码。

    /**
         * 制作多参数多文件消息并进行请求
         * @param urlString 目标url
         * @param mutiFileList 含有文件信息的List<map>,最简单的情况是map中包含文件名称和路径,每个map表示一个文件。
         * @param params 普通参数map,key作为参数名称,value作为参数值
         * @return
         */
        public String httpUploadMutiFile(String urlString,List<Map<String, Object>> mutiFileList, Map<String, String> params){
            String repString = null;
            InputStream is = null;
            OutputStream out = null;
            //定义boundarystr
            final String BOUNDARYSTR = Long.toHexString(System.currentTimeMillis());
            //定义回车换行
            final String CRLF = "
    ";
            final String BOUNDARY = "--"+BOUNDARYSTR+CRLF;
            StringBuilder paramsText = new StringBuilder();
            byte[] paraTextByte;
            try{
                //首先制作普通参数信息
                if(params != null && params.size() != 0){
    
                    for(String key:params.keySet()){
                        paramsText.append(BOUNDARY);
                        paramsText.append("Content-Disposition: form-data;name=""+key+"""+CRLF);
                        paramsText.append("Content-type: text/plain"+CRLF+CRLF);
                        paramsText.append(params.get(key));
                        paramsText.append(CRLF);
                    }
                    paraTextByte =  paramsText.toString().getBytes("UTF-8");
                }else{
                    //
                    paraTextByte = null;
                }
            }catch (Exception e){
                e.printStackTrace();
                paraTextByte = null;
            }
            //写入参数部分信息
            try{
                //先制作请求头
                URL url = new URL(urlString);
                HttpURLConnection con = (HttpURLConnection) url.openConnection();
                con.setDoInput(true);
                con.setDoOutput(true);
                con.setRequestMethod("POST");
                con.setRequestProperty("connection", "Keep-Alive");
                con.setRequestProperty("Charset", "UTF-8");
                con.setRequestProperty("Content-type", "multipart/form-data;boundary=" + BOUNDARYSTR);
                // 获得输出流
                out = new DataOutputStream(con.getOutputStream());
                // 写入普通参数部分
                if(paraTextByte != null && paraTextByte.length > 0){
                    out.write(paraTextByte);
                }
                //写入文件信息
                Map<String, Object> fileInfoMap;
                for(int i = 0; i < mutiFileList.size(); i++){
                    fileInfoMap = mutiFileList.get(i);
                    //当前文件map存有文件名称(uuid型)和文件的中文名称,以及文件的字节数据
                    // 如果文件过大,不建议使用该方式,建议传递文件路径再读取写入。
                    String fileName = (String)fileInfoMap.get("fileName");
                    String suffix = fileName.substring(fileName.indexOf("."));
                    String chFileName = fileInfoMap.get("chName")+suffix;
                    StringBuilder sb = new StringBuilder();
                    sb.append(BOUNDARY);
                    sb.append("Content-Disposition: form-data;name=""+chFileName+"";filename=""
                            + chFileName + """+CRLF);
                    //sb.append("Content-Type:application/octet-stream"+CRLF+CRLF);
                    //文件均是jpg图片类型
                    sb.append("Content-Type:image/jpg"+CRLF+CRLF);
                    out.write(sb.toString().getBytes());
    
                    //写入输出流
                    byte[] fileData = (byte[])fileInfoMap.get("data");
                    /**
                     * 如果map里存储的是文件路径,那么可以使用FileInputStream读取文件信息,并写入到OutputStream中。
                     * 具体就不写了。
                     */
                    out.write(fileData);
                    out.write(CRLF.getBytes("UTF-8"));
                }
                byte[] foot = ("--" + BOUNDARYSTR + "--"+CRLF).getBytes("UTF-8");// 定义最后数据分隔线
                out.write(foot);
                out.flush();
                is = con.getInputStream();
                repString = ioTool.getStringFromInputStream(is);
            }catch (Exception e){
                e.printStackTrace();
                logger.error("往业务系统写入文件失败:"+e.getMessage());
            }
            return repString;
    
        }

    下面是数据的解析。

    以SpringMVC配置的HTTP接口为例。但具体的解析写法和Spring没什么关系。

    在写这篇博客之前,我尝试使用了ServletFileUpload类和request.getParts()两种方法来解析。前一种的好处是有很多便利的Servlet  API可以使用。但是,HTTP请求体内容只能解析一次。这意味着,在进行解析前我们不能使用以下方法操作request。https://stackoverflow.com/questions/13881272/servletfileuploadparserequestrequest-returns-an-empty-list

    request.getParameter();
    request.getParameterMap();
    request.getParameterNames();
    request.getParameterValues();
    request.getReader();
    request.getInputStream();

    感觉不是很灵活。在servlet3.0后,request增加了getPart()和getParts()方法,利用这些方法处理文件数据表单可能要好一些。

    @RequestMapping(value = "uploadFile",method = RequestMethod.POST)
        public void handleMutiPartForm(HttpServletRequest request, HttpServletResponse response){
            //可以使用request.getParameter()获取普通参数数据
            String param1 =  request.getParameter("param1");
            String param2 = request.getParameter("param2");
            //获取parts
            Collection<Part> parts;
            try{
                parts = request.getParts();
            }catch (IOException ioe){
                parts = null;
            }catch (ServletException se){
    
                parts = null;
            }
            if(parts != null){
                //遍历parts
                //因为所有的参数信息都会在Collection<Part>中体现
                //这里只需要获取文件部分
                String saveFilePath = "D://FileUpload/";
                byte[] bytes = new byte[512];
                for(Part part:parts){
                    //Content-type: image/* 图片类型数据
                    if(part.getContentType().startsWith("image")){
                        String fileName = part.getSubmittedFileName();
                        String paramName = part.getName();
                        try{
                            InputStream ins = part.getInputStream();
                            String filePath = saveFilePath+fileName;
                            File file = new File(filePath);
                            if(!file.exists()){
                                file.createNewFile();
                            }
                            FileOutputStream fos = new FileOutputStream(file);
                            while(ins.read(bytes, 0, 512) != -1){
                                fos.write(bytes);
                            }
                            ins.close();
                            fos.close();
                        }catch (IOException ioe){
    
                        }
                    }
    
                }
            }
            try{
                response.getWriter().write("返回的信息");
            }catch (IOException ioe){
    
            }
        }

    RFC相关介绍:

    http://www.faqs.org/rfcs/rfc2388.html

    stackoverflow相关问题:

    https://stackoverflow.com/questions/2793150/using-java-net-urlconnection-to-fire-and-handle-http-requests/

  • 相关阅读:
    Linux:CentOS-7配置VMware-15.5与本机IP同网段
    SpringCloud:Zuul路由配置超时问题
    SpringCloud:路由ZUUL的配置详解
    SpringCloud:扩展zuul配置路由访问
    给source insight添加.cc的C++文件后缀识别(转载)
    Linux下共享库嵌套依赖问题 (转载)
    Ubuntu 下编译libjingle-0.6.14 (转载)
    什么是「穷人思维」?
    谁上北大是能力说了算,不该是教育部(转载)
    Installing cmake 2.8.8 or higher on Ubuntu 12.04 (Precise Pangolin) (转载)
  • 原文地址:https://www.cnblogs.com/derry9005/p/7727799.html
Copyright © 2011-2022 走看看