zoukankan      html  css  js  c++  java
  • asp.net大文件(视频)上传控件

    前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对 Http 协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。

    一. Http协议原理简介 

        HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。

        简单来说,就是一个基于应用层的通信规范:双方要进行通信,大家都要遵守一个规范,这个规范就是HTTP协议。

     1.特点:

      (1)支持客户/服务器模式。

      (2)简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

      (3)灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

      (4)无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

      (5)无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

      注意:其中(4)(5)是面试中常用的面试题。虽然HTTP协议(应用层)是无连接,无状态的,但其所依赖的TCP协议(传输层)却是常连接、有状态的,而TCP协议(传输层)又依赖于IP协议(网络层)。

     2.HTTP消息的结构

     (1)Request 消息分为3部分,第一部分叫请求行, 第二部分叫http header消息头, 第三部分是body正文,header和body之间有个空行, 结构如下图

     (2)Response消息的结构, 和Request消息的结构基本一样。 同样也分为三部分,第一部分叫request line状态行, 第二部分叫request header消息体,第三部分是body正文, header和body之间也有个空行,  结构如下图

    下面是使用Fiddler捕捉请求baidu的Request消息机构和Response消息机构:

    因为没有输入任何表单信息,故request的消息正文为空,大家可以找一个登录的页面试试看。

    先到这里,HTTP协议的知识网上很丰富,在这里就不再熬述了。

    二. 文件上传的三种实现

    1. Jsp/servlet 实现文件上传

    这是最常见也是最简单的方式

    (1)实现文件上传的Jsp页面 

    (2)负责接文件的FileUploadServlet

    import java.io.File;

    import java.io.FileOutputStream;

    import java.io.IOException;

    import java.io.InputStream;

    import javax.servlet.ServletException;

    import javax.servlet.http.HttpServlet;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;

    import org.apache.log4j.Logger;

    // @WebServlet(name = "FileLoadServlet", urlPatterns = {"/fileload"})

    public class FileLoadServlet extends HttpServlet {

        private static Logger logger = Logger.getLogger(FileLoadServlet.class);

        private static final long serialVersionUID = 1302377908285976972L;

        @Override

        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

            logger.info("------------ FileLoadServlet ------------");

            if (request.getContentLength() > 0) {           

                   InputStream inputStream = null;

                   FileOutputStream outputStream = null;              

                try {               

                    inputStream = request.getInputStream();

                    // 给新文件拼上时间毫秒,防止重名

                    long now = System.currentTimeMillis();

                    File file = new File("c:/", "file-" + now + ".txt");

                    file.createNewFile();

                    outputStream = new FileOutputStream(file);

                      byte temp[] = new byte[1024];

                      int size = -1;

                      while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完

                          outputStream.write(temp, 0, size);

                      }               

                      logger.info("File load success.");

                  } catch (IOException e) {

                      logger.warn("File load fail.", e);

                      request.getRequestDispatcher("/fail.jsp").forward(request, response);

                  } finally {

                      outputStream.close();

                      inputStream.close();

                  }

              }       

              request.getRequestDispatcher("/succ.jsp").forward(request, response);

          }   

      }

    FileUploadServlet的配置,推荐采用servlet3.0注解的方式更方便

    <servlet>

        <servlet-name>FileLoadServlet</servlet-name>

        <servlet-class>com.juxinli.servlet.FileLoadServlet</servlet-class>

    </servlet>

    <servlet-mapping>

        <servlet-name>FileLoadServlet</servlet-name>

        <url-pattern>/fileload</url-pattern>

    </servlet-mapping>

    (3)运行效果

    点击"submit"

    页面转向文件上传成功的页面,再去C盘看看,发现多了一个文件:file-1433417127748.txt,这个就是刚上传的文件

    我们打开看看,发现和原来的文本有些不一样

    ​             

    结合前面讲的HTTP协议的消息结构,不难发现这些文本就是去掉"请求头"后的"Request消息体"。所以,如果要得到与上传文件一致的文本,还需要一些字符串操作,这些就留给大家了。

    另外,大家可以试试一个Jsp页面上传多个文件,会有不一样的精彩哦o(∩_∩)o ,不解释。

    2. 模拟Post请求/servlet 实现文件上传

    刚才我们是使用Jsp页面来上传文件,假如客户端不是webapp项目呢,显然刚才的那种方式有些捉襟见衬了。

    这里我们换种思路,既然页面上通过点击可以实现文件上传,为何不能通过HttpClient来模拟浏览器发送上传文件的请求呢。关于HttpClient ,大家可以自己去了解。

     (1)还是这个项目,启动servlet服务

     (2)模拟请求的FileLoadClient

    import java.io.BufferedReader;

    import java.io.File;

    import java.io.InputStream;

    import java.io.InputStreamReader;

    import org.apache.commons.httpclient.HttpClient;

    import org.apache.commons.httpclient.HttpStatus;

    import org.apache.commons.httpclient.methods.PostMethod;

    import org.apache.commons.httpclient.methods.multipart.FilePart;

    import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;

    import org.apache.commons.httpclient.methods.multipart.Part;

    import org.apache.log4j.Logger;

    public class FileLoadClient {

        private static Logger logger = Logger.getLogger(FileLoadClient.class);

        public static String fileload(String url, File file) {

            String body = "{}";

            if (url == null || url.equals("")) {

                return "参数不合法";

            }

            if (!file.exists()) {

                return "要上传的文件名不存在";

            }

            PostMethod postMethod = new PostMethod(url);

            try {           

                // FilePart:用来上传文件的类,file即要上传的文件

                FilePart fp = new FilePart("file", file);

                Part[] parts = { fp };

                // 对于MIME类型的请求,httpclient建议全用MulitPartRequestEntity进行包装

                MultipartRequestEntity mre = new MultipartRequestEntity(parts, postMethod.getParams());

                postMethod.setRequestEntity(mre);

                HttpClient client = new HttpClient();

                // 由于要上传的文件可能比较大 , 因此在此设置最大的连接超时时间

                client.getHttpConnectionManager().getParams() .setConnectionTimeout(50000);

                int status = client.executeMethod(postMethod);

                if (status == HttpStatus.SC_OK) {

                    InputStream inputStream = postMethod.getResponseBodyAsStream();

                    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

                    StringBuffer stringBuffer = new StringBuffer();

                    String str = "";

                    while ((str = br.readLine()) != null) {

                        stringBuffer.append(str);

                    }               

                    body = stringBuffer.toString();               

                } else {

                    body = "fail";

                }

            } catch (Exception e) {

                logger.warn("上传文件异常", e);

            } finally {

                // 释放连接

                postMethod.releaseConnection();

            }        

            return body;

        }   

       

        public static void main(String[] args) throws Exception {

            String body = fileload("http://localhost:8080/jsp_upload-servlet/fileload", new File("C:/1111.txt"));

            System.out.println(body);

        }    

    }

    (3)在Eclipse中运行FileLoadClient程序来发送请求,运行结果:

    <html><head>  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body><h2>File upload success</h2><a href="index.jsp">return</a></body></html>

    打印了:文件上传成功的succ.jsp页面

    有没有发现什么,是不是和前面Jsp页面上传的结果类似?对的,还是去掉"请求头"后的"Request消息体"。 

    这种方式也很简单,负责接收文件的FileUploadServlet没有变,只要在客户端把文件读取到流中,然后模拟请求servlet就行了。

     3.模拟Post请求/Controller(SpringMvc)实现文件上传

     终于到第三种方式了,主要难点在于搭建maven+jetty+springmvc环境,接收文件的service和模拟请求的客户端 和上面相似。

     (1)模拟请求的FileLoadClient未变

    import java.io.BufferedReader;

    import java.io.File;

    import java.io.InputStream;

    import java.io.InputStreamReader;

    import org.apache.commons.httpclient.HttpClient;

    import org.apache.commons.httpclient.HttpStatus;

    import org.apache.commons.httpclient.methods.PostMethod;

    import org.apache.commons.httpclient.methods.multipart.FilePart;

    import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;

    import org.apache.commons.httpclient.methods.multipart.Part;

    import org.apache.log4j.Logger;

    public class FileLoadClient {   

        private static Logger logger = Logger.getLogger(FileLoadClient.class);

        public static String fileload(String url, File file) {

            String body = "{}";       

            if (url == null || url.equals("")) {

                return "参数不合法";

            }

            if (!file.exists()) {

                return "要上传的文件名不存在";

            }

            PostMethod postMethod = new PostMethod(url);       

            try {           

                // FilePart:用来上传文件的类,file即要上传的文件

                FilePart fp = new FilePart("file", file);

                Part[] parts = { fp };

                // 对于MIME类型的请求,httpclient建议全用MulitPartRequestEntity进行包装

                MultipartRequestEntity mre = new MultipartRequestEntity(parts, postMethod.getParams());

                postMethod.setRequestEntity(mre);

                HttpClient client = new HttpClient();

                // 由于要上传的文件可能比较大 , 因此在此设置最大的连接超时时间

                client.getHttpConnectionManager().getParams() .setConnectionTimeout(50000);

                int status = client.executeMethod(postMethod);

                if (status == HttpStatus.SC_OK) {

                    InputStream inputStream = postMethod.getResponseBodyAsStream();

                    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));

                    StringBuffer stringBuffer = new StringBuffer();

                    String str = "";

                    while ((str = br.readLine()) != null) {

                        stringBuffer.append(str);

                    }               

                    body = stringBuffer.toString();                

                } else {

                    body = "fail";

                }

            } catch (Exception e) {

                logger.warn("上传文件异常", e);

            } finally {

                // 释放连接

                postMethod.releaseConnection();

            }       

            return body;

        }   

        public static void main(String[] args) throws Exception {

            String body = fileload("http://localhost:8080/fileupload/upload", new File("C:/1111.txt"));

            System.out.println(body);

        }

    }

    (2)servlet换为springMvc中的Controller

    import java.io.File;

    import java.io.FileOutputStream;

    import java.io.IOException;

    import java.io.InputStream;

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;

    import org.apache.log4j.Logger;

    import org.springframework.stereotype.Controller;

    import org.springframework.web.bind.annotation.RequestMapping;

    import org.springframework.web.bind.annotation.RequestMethod;

    @Controller

    @RequestMapping("/fileupload")

    public class FileUploadService {

        private Logger logger = Logger.getLogger(FileUploadService.class);

        @RequestMapping(consumes = "multipart/form-data", value = "/hello", method = RequestMethod.GET)

        public void hello(HttpServletRequest request, HttpServletResponse response) throws IOException {        

            response.getWriter().write("Hello, jetty server start ok.");

        }

        @RequestMapping(consumes = "multipart/form-data", value = "/upload", method = RequestMethod.POST)

        public void uploadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {

            String result = "";

            if (request.getContentLength() > 0) {           

                   InputStream inputStream = null;

                   FileOutputStream outputStream = null;              

                try {

                    inputStream = request.getInputStream();

                    // 给新文件拼上时间毫秒,防止重名

                    long now = System.currentTimeMillis();

                    File file = new File("c:/", "file-" + now + ".txt");

                    file.createNewFile();

                    outputStream = new FileOutputStream(file);               

                    byte temp[] = new byte[1024];

                    int size = -1;

                    while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完

                        outputStream.write(temp, 0, size);

                    }

                    logger.info("File load success.");

                    result = "File load success.";

                } catch (IOException e) {

                    logger.warn("File load fail.", e);

                    result = "File load fail.";

                } finally {

                    outputStream.close();

                    inputStream.close();

                }

            }       

            response.getWriter().write(result);

        }

    }

     (3)启动jetty的核心代码,在Eclipse里面右键可以启动,也可以把项目打成jar报启动

    import org.apache.log4j.Logger;

    import org.eclipse.jetty.server.Connector;

    import org.eclipse.jetty.server.Server;

    import org.eclipse.jetty.server.ServerConnector;

    import org.eclipse.jetty.webapp.WebAppContext;

    public class Launcher

    {   

        private static Logger logger = Logger.getLogger(Launcher.class);

        private static final int PORT = 8080;

        private static final String WEBAPP = "src/main/webapp";

        private static final String CONTEXTPATH = "/";

        private static final String DESCRIPTOR = "src/main/webapp/WEB-INF/web.xml";

        /*

         * 创建 Jetty Server,指定其端口、web目录、根目录、web路径

         * @param port

         * @param webApp

         * @param contextPath

         * @param descriptor

         * @return Server

         */

        public static Server createServer(int port, String webApp, String contextPath, String descriptor) {

            Server server = new Server();

            //设置在JVM退出时关闭Jetty的钩子

            //这样就可以在整个功能测试时启动一次Jetty,然后让它在JVM退出时自动关闭

            server.setStopAtShutdown(true);

            ServerConnector connector = new ServerConnector(server);

            connector.setPort(port);

            //解决Windows下重复启动Jetty不报告端口冲突的问题

            //在Windows下有个Windows + Sun的connector实现的问题,reuseAddress=true时重复启动同一个端口的Jetty不会报错

            //所以必须设为false,代价是若上次退出不干净(比如有TIME_WAIT),会导致新的Jetty不能启动,但权衡之下还是应该设为False

            connector.setReuseAddress(false);

            server.setConnectors(new Connector[]{connector});

            WebAppContext webContext = new WebAppContext(webApp, contextPath);

            webContext.setDescriptor(descriptor);

            // 设置webapp的位置

            webContext.setResourceBase(webApp);

            webContext.setClassLoader(Thread.currentThread().getContextClassLoader());

            server.setHandler(webContext);       

            return server;

        }

        /**

         * 启动jetty服务

         */

        public void startJetty() {

            final Server server = Launcher.createServer(PORT, WEBAPP, CONTEXTPATH, DESCRIPTOR);

            try {

                server.start();

                server.join();           

            } catch (Exception e) {

                logger.warn("启动 jetty server 失败", e);

                System.exit(-1);

            }

        }

        public static void main(String[] args) {       

            (new Launcher()).startJetty();

            // jetty 启动后的测试url

            // http://localhost:8080/fileupload/hello

        }   

    }

    springMvc的配置不贴了,大家可以下载源码下来看。

    (4)运行效果

    上传包含1W个文件的文件夹,正常

    大型文件续传功能正常 。

    文件批量上传正常

    服务器中已经根据日期+GUID生成了目录

    数据库中也有记录

    后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/

    欢迎入群一起讨论:374992201

  • 相关阅读:
    POJ 3710 Christmas Game#经典图SG博弈
    POJ 2599 A funny game#树形SG(DFS实现)
    POJ 2425 A Chess Game#树形SG
    LeetCode Array Easy 122. Best Time to Buy and Sell Stock II
    LeetCode Array Easy121. Best Time to Buy and Sell Stock
    LeetCode Array Easy 119. Pascal's Triangle II
    LeetCode Array Easy 118. Pascal's Triangle
    LeetCode Array Easy 88. Merge Sorted Array
    ASP.NET MVC 学习笔记之 MVC + EF中的EO DTO ViewModel
    ASP.NET MVC 学习笔记之面向切面编程与过滤器
  • 原文地址:https://www.cnblogs.com/songsu/p/14357664.html
Copyright © 2011-2022 走看看