Archive

Archive for December, 2007

PageCacheFilter2

December 31st, 2007 Sparkle No comments
/**
 * 2006-5-1
 * @author Sparkle
 */
public class PageCacheFilter2 implements Filter {
	private Set<string> cacheUrlSet = new HashSet<string>();
	private Set<string> scacheUrlSet = new HashSet<string>();
	private String baseCachePath, contentType;
 
	public void init(FilterConfig config) throws ServletException {
		String cacheUrl = config.getInitParameter("cacheUrl");
		if (cacheUrl != null) {
			StringTokenizer tk = new StringTokenizer(cacheUrl);
			while (tk.hasMoreTokens()) {
				String str = tk.nextToken().trim().toLowerCase();
				if (str.endsWith("?")) {
					str = str.substring(0, str.length() - 1);
					scacheUrlSet.add(str);
				}
				cacheUrlSet.add(str);
			}
		}
		baseCachePath = config.getInitParameter("cachePath");
		contentType = config.getInitParameter("contentType");
	}
 
	private int urlHashCode(HttpServletRequest request) {
		String uri = request.getRequestURI();
		String qStr = request.getQueryString();
		if (qStr != null) {
			uri += ('?' + qStr);
		}
		return Math.abs(uri.hashCode());
	}
 
	private String cacheFilePath(HttpServletRequest request) {
		String uri = request.getRequestURI();
		int hash = urlHashCode(request);
		if (scacheUrlSet.contains(uri.toLowerCase())) {
			String entityId = request.getParameter("entityId");
			if (StringUtils.isNotBlank(entityId)) {
				return baseCachePath + "/article/" + subDir(entityId)
						+ '/' + entityId + '/' + hash + ".wml";
			}
		}
		return baseCachePath + "/other/" + subDir2(String.valueOf(hash))
				+ '/' + hash + ".wml";
	}
 
	private String subDir(String str) {
		int length = str.length();
		if (length > 4) {
			return str.substring(0, length - 4);
		}
		return "0";
	}
 
	private String subDir2(String str) {
		int length = str.length();
		if (length > 4) {
			return str.substring(length - 4, length);
		}
		return str;
	}
 
	private void clearCache(HttpServletRequest request, File cacheFile) {
		if (scacheUrlSet.contains(request.getRequestURI().toLowerCase())) {
			File cacheDir = cacheFile.getParentFile();
			for (File file : cacheDir.listFiles()) {
				file.delete();
			}
			cacheDir.delete();
		} else {
			cacheFile.delete();
		}
	}
 
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		if (!cacheUrlSet.contains(request.getRequestURI().toLowerCase())) {
			chain.doFilter(req, res);
			return;
		}
 
		HttpServletResponse response = (HttpServletResponse) res;
 
		File cacheFile = new File(cacheFilePath(request));
		if (cacheFile.isFile()) {
			if (request.getParameter("refresh") != null) {
				clearCache(request, cacheFile);
			} else {
				try {
					BufferedReader reader = new BufferedReader(
							new FileReader(cacheFile));
					response.setContentType(contentType);
					String line;
					PrintWriter printWriter = response.getWriter();
					while ((line = reader.readLine()) != null) {
						printWriter.println(line);
					}
					printWriter.close();
					return;
				} catch (IOException e) {
					// ignore
					response.reset();
					System.out.println("read cache file error");
				}
			}
 
		}
 
		StringWriter strWriter = new StringWriter();
		boolean[] support = new boolean[1];
		support[0] = true;
		chain.doFilter(req, new ContentResponseWrapper(response, strWriter,
				support));
		if (support[0] && !response.isCommitted()) {
			String content = strWriter.getBuffer().toString();
 
			PrintWriter printWriter = response.getWriter();
			printWriter.println(content);
			printWriter.close();
 
			File cacheDir = cacheFile.getParentFile();
			if (!cacheDir.exists()) {
				cacheDir.mkdirs();
			}
			PrintWriter writer = new PrintWriter(cacheFile);
			writer.println(content);
			writer.close();
		}
 
	}
 
	public void destroy() {
	}
 
}

代码点评:这段代码的作用是,对指定的url进行cache,用了ContentRewriteFilter一样的做法来获取内容,因为具体的细节有些区别,没有继承ContentRewriteFilter而是直接编写代码。先判断是否有cache文件,如果有就输出给用户,但是如果连接上有refresh参数的话,就会清掉cache文件,如果没有cache文件,就会获取Servlet or JSP输出的内容,原原本本保存到磁盘,供下次用户访问使用。如果设置URL的时候,最后一位是问号,就会进入scacheUrlSet,这个是用于文章类型,主要的不同点是有分页,如果要refresh的话,应该要把所有分页的cache文件都清除。至于类名后面有个2,是因为这个是第二个版本。

(最近在考虑用Squid+Etag来实现一样的功能)

Categories: Uncategorized Tags: ,

ContentRewriteFilter

December 31st, 2007 Sparkle No comments
    /**
     * 2006-4-30
     * @author Sparkle
     */
    public abstract class ContentRewriteFilter implements Filter {
	protected boolean wantRewrite(HttpServletRequest request) {
		return true;
	}
 
	protected abstract String rewrite(String content,
			HttpServletRequest request, HttpServletResponse response);
 
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		if (!wantRewrite(request) || res.isCommitted()) {
			chain.doFilter(req, res);
			return;
		}
 
		HttpServletResponse response = (HttpServletResponse) res;
		StringWriter strWriter = new StringWriter();
		boolean[] support = new boolean[1];
		support[0] = true;
		chain.doFilter(req, new ContentResponseWrapper(response, strWriter,
				support));
		if (support[0] && !response.isCommitted()) {
			String content = strWriter.getBuffer().toString();
 
			content = rewrite(content, request, response);
 
			PrintWriter printWriter = response.getWriter();
			printWriter.println(content);
			printWriter.close();
		}
 
	}
 
	public void destroy() {
	}
 
	public void init(FilterConfig config) throws ServletException {
	}
    }
 
 
    public class ContentResponseWrapper extends HttpServletResponseWrapper {
	private Writer writer;
 
	private boolean[] support;
 
	public ContentResponseWrapper(HttpServletResponse response, Writer writer,
			boolean[] support) {
		super(response);
		this.writer = writer;
		this.support = support;
	}
 
	@Override
	public ServletOutputStream getOutputStream() throws IOException {
		support[0] = false;
		return super.getOutputStream();
	}
 
	@Override
	public PrintWriter getWriter() throws IOException {
		return new PrintWriter(writer);
	}
 
    }

代码点评:这段代码的作用是,获得Servlet or JSP输出的内容进行改写操作,这是一个基类,继承之后重载abstract rewrite即可获得输出的内容进行改写。这段代码在实际环境工作之后做了不少的修正,对response的commit状态,和是否成功获取内容都有较好的判断。中途用了一个[0]的技巧回传了一个boolean值。使用这个代码实现了动态插入广告,个人短消息等资料,还有sessionid自动补充(!)等等功能

Categories: Uncategorized Tags:

CryptUtil

December 31st, 2007 Sparkle No comments
    /**
     * 2006-6-10
     * @author Sparkle
     */
    public class CryptUtil {
	/**
	 * 将一个数字转换成0-9a-zA-z的62进制
	 */
	public static String encodeLongToString(long i) {
		if (i == 0) {
			return "0";
		} else if (i < 0) {
			throw new IllegalArgumentException();
		}
		StringBuffer buffer = new StringBuffer();
		while (i > 0) {
			buffer.insert(0, encodeIntToChar((int) (i % 62)));
			i /= 62;
		}
		return buffer.toString();
	}
 
	/**
	 * 将0-9a-zA-z的62进制转换成一个数字
	 */
	public static long decodeStringToLong(String s) {
		long i = 0;
		for (int index = 0; index < s.length(); index++) {
			if (index > 0) {
				i *= 62;
			}
			i += decodeCharToInt(s.charAt(index));
		}
		return i;
	}
 
	/**
	 * 将数字作为顺序转换成单位的0-9a-zA-z<br />
	 * 0 -> '0'<br />
	 * 9 -> '9'<br />
	 * 10 -> 'a'<br />
	 * 35 -> 'z'<br />
	 * 36 -> 'A'<br />
	 * 61 -> 'Z'
	 */
	private static char encodeIntToChar(int i) {
		if (i >= 0 && i <= 9) {
			return (char) (i + 48);
		} else if (i >= 10 && i <= 35) {
			return (char) (i + 87);
		} else if (i >= 36 && i <= 61) {
			return (char) (i + 29);
		} else {
			throw new IllegalArgumentException();
		}
	}
 
	/**
	 * 将单位的0-9a-zA-z按顺序转换成数字<br />
	 * '0' -> 0<br />
	 * '9' -> 9<br />
	 * 'a' -> 10<br />
	 * 'z' -> 35<br />
	 * 'A' -> 36<br />
	 * 'Z' -> 61
	 */
	private static int decodeCharToInt(char c) {
		if (c >= '0' && c <= '9') {
			return c - 48;
		} else if (c >= 'a' && c <= 'z') {
			return c - 87;
		} else if (c >= 'A' && c <= 'Z') {
			return c - 29;
		} else {
			throw new IllegalArgumentException();
		}
	}
    }

代码点评:这段代码的作用是,转换出来的字符串会比16进制要短很多

Categories: Uncategorized Tags:

Apache、Nginx、Lighttpd对比

December 18th, 2007 Sparkle 2 comments

Apache

* 经典的Web服务器
* 除了慢没有别的缺点了
* 对了,Apache2对fcgi支持并不好
* 非常好用的proxy和proxy_ajp(很多人用它作为tomcat的前端)
* 不支持epoll(这年头,epoll几乎是性能的必备)

Nginx

* 速度快,占用资源少
* 杀手级的proxy和rewrite
* 非常不错的静态文件能力
* 最适合作为整个网站的前端服务(将php、svn等不同请求发送往后端apache)
* 其他功能马马虎虎

Lighttpd

* 杀手级的静态文件能力
* 杀手级的fcgi能力
* 不稳定的proxy模块

总体来说,如果你不确定应该用什么服务器,那就应该用Apache
但是稍微可以配置多个服务的情况下,做一个Nginx在最前端,然后把需要的功能转发给Apache是最好的选择
如果你打算跑fcgi,Lighttpd是不二的选择
如果你打算做图片服务器,独立的Lighttpd也是很好的选择

Categories: 互联网 Tags: , ,

消失的COS

December 15th, 2007 Sparkle 1 comment

一直在用COS作为上传组件
最近升级其中一个应用到Spring 2.5
竟然报错,找不到org.springframework.web.multipart.cos.CosMultipartResolver类
Spring 2.5把web和webmvc独立出来已经觉得有点奇怪得了
重新检查了一下,在spring-web里面找到

> org.springframework.web.multipart.commons

但是找不到

> org.springframework.web.multipart.cos

立刻查看2.5的reference,里面还在提及怎么使用COS

对比查看2.0和2.5的MultipartResolver的javadoc
在2.5发现这样一句

> There is only one concrete implementation included in Spring, as of Spring 2.5:
> org.springframework.web.multipart.commons.CommonsMultipartResolver for Jakarta Commons FileUpload

COS支持的确被去掉了,晕
一直一来,许多人的观点都是认为commons-fileupload是有内存泄露的,用COS比较保险

为了确认一下被去掉的原因,到spring forum里面search了一番,没有提及到这次的修改

在spring的changelog里面search了一番,终于找到说明的地方

> Changes in version 2.1 M1 (2007-05-13)
> removed CosMultipartResolver (as part of generally removing COS support from the Spring codebase)

但是没有说原因

晕了

看来只能试试用commons-fileupload

Categories: Uncategorized Tags:

Linux文件句柄数量问题

December 11th, 2007 Sparkle No comments

在Linux下面部署应用的时候,有时候会遇上Socket/File: Can’t open so many files的问题,其实Linux是有文件句柄限制的(就像WinXP?),而且默认不是很高,一般都是1024,作为一台生产服务器,其实很容易就达到这个数量,因此我们需要把这个值改大一些。

大概知道ulimit这个命令是相关的,上Google搜索了一下,大多数说的很含糊,也没有统一说一下,经过两个小时看了不少文章终于弄清楚ulimit相关的一些配置问题。

我们可以用ulimit -a来查看所有限制值,我只关心文件句柄数量的问题

open files (-n) 1024

这个就是限制数量

这里,有很多ulimit的文章都说的很含糊,究竟这个1024是系统的限制,还是用户的限制呢。其实,这个是用户限制来的,完整的说法,应该是当前用户准备要运行的程序的限制。

1、这个限制是针对单个程序的限制
2、这个限制不会改变之前已经运行了的程序的限制
3、对这个值的修改,退出了当前的shell就会消失

比如说,我先运行了一个程序A,然后通过ulimit修改了限制为2048,然后运行B,然后退出了shell再登录,然后运行C。那就只有B可以打开2048个句柄。

如果我们需要改变整体的限制值,或者我们运行的程序是系统启动的,应该怎么处理呢

其中一个方法,是想ulimit修改命令放入/etc/profile里面,但是这个做法并不好

正确的做法,应该是修改/etc/security/limits.conf
里面有很详细的注释,比如

* soft nofile 2048
* hard nofile 32768

就可以将文件句柄限制统一改成软2048,硬32768

这里涉及另外一个问题,什么是软限制,什么是硬限制
硬限制是实际的限制,而软限制,是warnning限制,只会做出warning
其实ulimit命令本身就有分软硬设置,加-H就是硬,加-S就是软
默认显示的是软限制,如果修改的时候没有加上的话,就是两个一起改

配置文件最前面的一位是domain,设置为星号代表全局,另外你也可以针对不同的用户做出不同的限制

修改了,重新登录用ulimit一开就立刻生效了,不过之前启动过的程序要重新启动才能使用新的值。我用的是CentOS,似乎有些系统需要重启才能生效。

ulimit其实就是对单一程序的限制

那系统总限制呢
其实是在这里,/proc/sys/fs/file-max
可以通过cat查看目前的值,echo来立刻修改

另外还有一个,/proc/sys/fs/file-nr
只读,可以看到整个系统目前使用的文件句柄数量

查找文件句柄问题的时候,还有一个很实用的程序lsof
可以很方便看到某个进程开了那些句柄
也可以看到某个文件/目录被什么进程占用了(umount不了就可以看谁的问题了)

Categories: Uncategorized Tags:

为什么选择trac

December 10th, 2007 Sparkle No comments

在一个开发团队里面,bug tracker工具是很重要的。软件一定有bug,我们需要一个跟踪bug的工具,谁报告的bug?详细情况怎么样?别人能不能还原?

需要修正吗?优先度?已经修正了吗?等等。虽然我说了那么多需求,但实际上太全面的功能也会导致复杂性,呵呵。

最近需要在公司部署一个bug tracker,目前可以选择的工具也有不少

1、Bugzilla
功能非常强大,定制性也很强,Mylyn支持最好
可惜,安装困难,板式不直观
=====
正好提到Mylyn,其实我的选择,很大程度受到Mylyn的支持情况影响

2、JIRA
功能强大,定制性也非常强
可惜太复杂了

3、Mantis
我一直对这个软件印象非常好
清晰的版面,用上去就非常舒服
可惜新版本迟迟未release
另一个重要的原因是Mylyn对他支持非常差

4、Trac
Mylyn官方支持三个软件,Bugzilla,JIRA,Trac
因此我也在这三个软件中挑选了很久
Trac的安装比较麻烦(当然后来知道并不麻烦)
功能也中规中矩,有最基本的功能(其实我也不想让软件做太多功能,始终团队的能力为主)
定制能力很差(其实我不需要定制)
同时还包含一个wiki(正好还在讨论公司内部要不要弄一个wiki)
对mysql支持不太好(其实sqlite的效果也很不错,备份也方便)
认证模式刚好可以跟svn一样(太棒了)
真是越用越喜欢,呵呵

Categories: 互联网 Tags: , , ,