最初 node 版本在这里。
源码下载,单 jsp 文件 http://pan.baidu.com/s/1gfoDy0r (百度云提供)
App 客户端加载图片的时候,在服务端需要进行相关的优化,否则会给流量和占用内存带来消耗和压力。起初想到了利用 node 搭建一个简易的图片服务器来完成。遗憾的是,因为 Centos 平台上 node-images二进制包的问题,导致不能顺利使用 node 方案。于是迫不得已,改用 Java 来完成。及后,考虑到逻辑不复杂和部署的简易,特意将 Java 改成 JSP 页面调用,而非 Servlet。JSP 大家都比较熟悉,这里再提一提它的优点:1、可以“过程式”编码,不用考虑包等的问题。虽然面向对象 OO 必须强调,但某些时候“过程式”足矣,结构简单明晰;2、无须重启服务器,修改立刻看到效果,减少运维的工作。有同学说过,JSP 问题:一、混搭 HTML 代码不好看,这个确实是如果开发者可以注意下,也是能够规避的;二、写起来太慢。噢~我现在不是第一时间在 JSP 里面写 Java,而是在 Java 类里面写好了、测试好了再 copy 到 JSP(原 Java 文件在这里),稍微改动下,有点像手动打包的过程,呵呵。
值得一提的是,Tomcat 7 下 JSP 默认的 Java 语法仍旧是 1.6 的。在 JSP 里面嵌入 Java 1.7 特性的代码会抛出“Resource specification not allowed here for source level below 1.7”的异常。于是需要修改 Tomcat/conf/web.xml 里面的配置文件,找到 <servlet> 节点,加入下面粗体部分才可以。注意是 jsp 节点,不是 default 节点(很相似)。
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>compilerSourceVM</param-name> <param-value>1.7</param-value> </init-param> <init-param> <param-name>compilerTargetVM</param-name> <param-value>1.7</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
调用例子:
img_resize.jsp?url=http://desk.fd.zol-img.com.cn/t_s1920x1200c5/g5/M00/0F/04/ChMkJ1dGqWOITU3xAA5PBuQ0iRMAAR6mAAAAAAADk8e103.jpg&h=400
先说说图片处理的流程。
- 首先从 url 参数获取图片地址,该参数必填
- 通过 HTTP HEAD 请求检测是否 404,以及图片文件大小
- 如果小于 100kb (1024 * 100)则无须压缩,响应对象发出 302 重定向 url 地址
- 如果大于 100kb,则读取文件流,输入到 java.awt.Image 对象
- 如果没有指定高、宽,那么直接在浏览器输出图片(原尺寸输出)
- 若 w / h 指定其一则按图片比例缩放,w:int = 宽度,可选,h:init = 高度,可选。
- 通过图片原尺寸计算出比例,求得 w / h
- 通过 setResize(Image img, int height, int width) 方法设置图片大小
可能还有考虑不周的情况,望大家指点提拨。
JSP 页面调用方式就是:
<%@page pageEncoding="utf-8" import="java.io.*, java.net.*, javax.imageio.ImageIO, java.awt.Image, java.awt.image.*"%> <% // JSP 实用程序之简易图片服务器 // http://blog.csdn.net/zhangxin09/article/details/51523489 // sp42 2016-5-30 String url = request.getParameter("url"); if(url == null){ out.println("缺少 url 参数!"); return; } try{ getImg(url, request, response); //清除输出流,防止释放时被捕获异常 out.clear(); out = pageContext.pushBody(); } catch(Exception e) { out.println("Error:" + e); } %>
依赖函数仅仅是以下几个:
<%! /** * 主要的函数 */ public static void getImg(String url, HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("请求地址:" + url); URL urlObj = new URL(url); HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection(); int imgSize = getSize(conn, urlObj.getHost()); if (imgSize < (1024 * 100)) { response.sendRedirect(url);// 发送重定向 return; } else { // System.out.println("BigImg"); String imgType = getImgType(url); response.setContentType(getContentType(imgType)); try ( InputStream is = new URL(url).openStream(); ServletOutputStream op = response.getOutputStream(); ){ String height = request.getParameter("h"), width = request.getParameter("w"); if (height != null && width != null) { BufferedImage bufImg = setResize(ImageIO.read(is), Integer.parseInt(height), Integer.parseInt(width)); ImageIO.write(bufImg, imgType, op); } else if (height != null) { Image img = ImageIO.read(is);// 将输入流转换为图片对象 int[] size = resize(img, Integer.parseInt(height), 0); BufferedImage bufImg = setResize(img, size[0], size[1]); ImageIO.write(bufImg, imgType, op); } else if (width != null) { Image img = ImageIO.read(is);// 将输入流转换为图片对象 int[] size = resize(img, 0, Integer.parseInt(width)); BufferedImage bufImg = setResize(img, size[0], size[1]); ImageIO.write(bufImg, imgType, op); } else { // 直接写浏览器 byte[] buf = new byte[1024]; int len = is.read(buf, 0, 1024); while (len != -1) { op.write(buf, 0, len); len = is.read(buf, 0, 1024); } } } } } /** * 返回 Content type * @param imgType * @return */ private static String getContentType(String imgType) { switch (imgType) { case "jpg": return "image/jpeg"; case "gif": return "image/gif"; case "png": return "image/png"; default: return null; } } /** * 获取 url 最后的 .jpg/.png/.gif * @param url * @return */ private static String getImgType(String url) { String[] arr = url.split("/"); arr = arr[arr.length - 1].split("\\."); String t = arr[1]; return t.replace('.', ' ').trim().toLowerCase(); } /** * 缩放比例 * * @param img * 图片对象 * @param height * 高 * @param width * 宽 * @return */ public static int[] resize(Image img, int height, int width) { int oHeight = img.getHeight(null), oWidth = img.getWidth(null); double ratio = (new Integer(oHeight)).doubleValue() / (new Integer(oWidth)).doubleValue(); if(width != 0) { height = (int) (ratio * width); }else { width = (int) ( height / ratio); } return new int[]{height, width}; } /** * 完成设置图片大小 * * @param img * 图片对象 * @param height * 高 * @param width * 宽 * @return 缓冲的图片对象 */ public static BufferedImage setResize(Image img, int height, int width) { BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); bufImg.getGraphics().drawImage(img, 0, 0, width, height, null); return bufImg; } /** * * @param conn * @param referer * @return */ public static int getSize(HttpURLConnection conn, String referer) { try { conn.setRequestMethod("HEAD"); conn.setInstanceFollowRedirects(false); // 必须设置false,否则会自动redirect到Location的地址 conn.addRequestProperty("Accept-Charset", "UTF-8;"); conn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Firefox/3.6.8"); conn.addRequestProperty("Referer", referer); conn.setConnectTimeout(5000);// 设置超时 conn.setReadTimeout(30000); InputStream body = null; try { body = conn.getInputStream(); } catch (UnknownHostException e) { throw new RuntimeException("未知地址!" + conn.getURL().getHost() + conn.getURL().getPath()); } catch (FileNotFoundException e) { throw new RuntimeException("404 地址!" + conn.getURL().getHost() + conn.getURL().getPath()); } catch (SocketTimeoutException e) { throw new RuntimeException("请求地址超时!" + conn.getURL().getHost() + conn.getURL().getPath()); } body.close(); // if (conn.getResponseCode() > 400) // 如果返回的结果是400以上,那么就说明出问题了 // LOGGER.warning("Err when got responseCode :" + // conn.getResponseCode() + getUrlStr()); // conn.connect();也就是打开流 } catch (IOException e) { e.printStackTrace(); return -1; } String sizeStr = conn.getHeaderField("content-length"); return Integer.parseInt(sizeStr); } %>
开发过程值得记住的几点:
- Url 和 Connection 貌似不能重复使用,于是两次请求(同一地址)得有两个 url 对象
- 调整图片比例函数比较简单,但编码有时得晓得不是写算术。
- 设定好正确的 图片ContentType
- 不知道动图 gif 压缩后是否只剩一帧?
不足之处,敬请大家提出。