JavaWeb开发中的字符编码问题的直截了当解决方案

2017-01-11 • 技术文章

解决方案

从容器字符编码配置、响应头设置、Meta字符集设置全部统一为UTF-8,不要通过自己写代码做层层转码。

容器:URI_ENCODING=UTF-8,USE_BODY_ENCODING_FOR_QUERY_STRING=true

ContentType:text/html; charset=UTF-8

Meta:charset=UTF-8

当然,对于用户可以手动拼参数的场景,比如定制百度搜索,那么应该具备自动探测编码的能力,而不是仅固定支持,百度可以自动识别queryString的编码是个很好的范例。

备注

HttpServletRequest.setCharacterEncoding方法仅仅只适用于设置POST提交的RequestBody部分的数据的编码而不是设置GET提交的queryString的编码。

HttpServletRequest.getPathInfo和getParameter返回的结果是由Servlet服务器解码过的。

HttpServletRequest.getRequestURI返回的字符串没有被Servlet服务器解码过。

编码随谈:加号与空格

URI把允许出现的字符分为保留未保留。百分号编码一个保留字符,其实是在这个字符的16进制ASCII值前面加上转义字符%,未保留字符不需要被百分号编码。对于那些不在保留字符和未保留字符范围内的字符,先转换为UTF-8字节序列,然后对其字节值使用百分号编码。在正常的编码解码流程中,编码的时候先把加号替换为%2B,然后把空格替换为加号;解码的时候先把加号替换为空格,再把%2B替换为加号,就是正常的。但是在已经urlencoded的内容中如果使用这二者不慎,就很有可能造成编解码不正确的问题了。

SSH会话保持基本设置

2016-03-27 • 技术文章

配置文件

1
2
/etc/ssh/ssh_config(Linux)
~/.ssh/config(MacOS)

新窗口连接同一服务器免重登

1
2
3
host *
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p

最后一行表示在指定目录下生成sock文件,保存会话记录。

避免SSH连接断开

为避免packet_write_wait: Connection to [IP]: Broken pipe连接断开的情况,可通过以下配置。

1. 客户端实现(其中55代表定时向SSH Server发送心跳的间隔秒数,下同):

1
2
host *
ServerAliveInterval 55

2. 命令实现:

1
ssh -o ServerAliveInterval=55 user@sshserver

3. 也可以在服务端sshd_config中配置,避免客户端单独配置:

1
ClientAliveInterval 55

操作系统虚拟化与Linux命名空间

2015-11-22 • 技术文章

虚拟化的本质需求来源于任务的隔离,也就是资源的隔离。所以,很多时候我们不是要在OS上虚拟出一个独立的OS,而是希望让虚拟机共享操作系统内核而又保持隔离性。

chroot

诞生于1979年的chroot是资源隔离的一种方案,chroot使一个进程把某个特定目录作为根目录,所有文件系统操作都被限定在该目录里进行。

但是chroot仅仅做到了文件系统层面的隔离是远远不够的,我们希望对进程、网络等各种资源进行更深层次的隔离而且希望能够实现对这些资源的审计。

Linux命名空间

Linux的命名空间机制为实现基于容器的虚拟化技术提供了很好的基础。

命名空间建立了系统的不同视图,只使用一个内核在一台物理机上运作,将全局资源都通过命名空间抽象起来,每个命名空间中的资源对其它命名空间是透明的。LXC就是利用这一特性实现了资源的隔离,由于没有多余的一层操作系统内核,容器比虚拟机更加轻量,启动更快,内存开销、调度开销也更小,更重要的是访问磁盘等IO设备不需要经过虚拟化层,没有性能损失。

当我们用fork或clone系统调用创建新进程时,有特定的标志位可以控制是与父进程共享命名空间,还是建立新的命名空间。命名空间的实现需要两个部分:每个子系统的命名空间结构,将此前所有的全局组件包装到命名空间中;将给定进程关联到所属各个命名空间的机制。子系统此前的全局属性现在封装到命名空间中,每个进程关联到一个选定的命名空间。每个可以感知命名空间的内核子系统都必须提供一个数据结构,将所有通过命名空间形式提供的对象集中起来。nsproxy结构体用于汇集指向特定于子系统的命名空间包装器的指针。

1
2
3
4
5
6
7
8
9
10
struct nsproxy {
    // 注释仅为匹配该行对应命名空间的标志位注解,不是本处引用代码的说明
    atomic_t count; 
    struct uts_namespace *uts_ns;   // 标志位CLONE_NEWUTS
    struct ipc_namespace *ipc_ns;   // 标志位CLONE_NEWIPC
    struct mnt_namespace *mnt_ns;   // 标志位CLONE_NEWNS
    struct pid_namespace *pid_ns;   // 标志位CLONE_NEWPID
    struct user_namespace *user_ns;
    struct net *net_ns; 
};

uts_namespace包含了运行内核的名称、版本、底层体系结构类型等信息(UTS即UNIX Timesharing System);ipc_namespace包含所有与进程间通信(IPC)有关的信息;mnt_namespace包含了文件系统的视图信息;pid_namespace报行了进程ID相关的信息;user_namespace包含了用于限制每个用户资源使用的信息;net_ns包含所有网络相关的命名空间参数;count是引用计数。

clone(child_exec, child_stack + STACKSIZE, clone_flags, &args)其中的clone_flags即为设置不同命名空间的标志位的或运算结果。如同时设定CLONE_NEWPID和CLONE_NEWIPC,那么不同命名空间的进程彼此不可见,也不能互相通信,这样就实现了进程间的隔离;CLONE_NEWUTS和CLONE_NEWNET一起使用,可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。

以上所有标志位都可以一起使用,为进程提供了一个独立的运行环境。LXC就是通过clone时设定这些flag,为进程创建一个有独立PID、IPC、FS、Network、UTS空间的容器。一个容器就是一个虚拟的运行环境,对容器内的进程是透明的,它会以为自己是直接在一个系统上运行的。

几种其它虚拟化方案

OpenVZ也是Linux内核里实现操作系统级虚拟化的解决方案之一,但相对LXC,可以分辨子系统中内存和磁盘的配额分配情况,可进行独立审计,且支持检查点、热迁移等更多特性,缺点是基于非常老的Linux内核设计,对新特性支持差。

Docker如今大红大紫,它将基础运行环境和依赖都打包在镜像中,且镜像是层次化的,基于增量修改(基于AUFS实现,基础目录A和增量目录B合并为目录C,冲突时以B为准,修改C写回B),低开销实现了版本控制和分发,软件运行在独立容器中避免冲突。Docker很好地解决了依赖与资源冲突的问题,大大降低了运维成本。

Chromium插件内容脚本与背景脚本消息传递实现

2015-06-01 • 技术文章

在Chromium浏览器插件的开发过程中,内容脚本(content script)和背景脚本(background script)的信息交互是非常常用的。然而由于Chromium的安全隔离机制,这种交互变得成本很高。笔者最近为了省去很多重复无意义的工作步骤,计划开发一款浏览器插件,经过对WebsiteIP插件源代码的研究以及数个小时的试错,终于在自己的插件中实现了这种交互。

1
2
3
4
5
6
7
8
9
10
11
// background: start listening
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
     sendResponse({key: request.action == "guessMyLocation" ? sender.tab.url : "Mars"});
});
 
// content script: send messages
docReady(function() {
    chrome.runtime.sendMessage({action: "guessMyLocation"}, function(response) {
        console.log("You're from " + response.key);
    }
});

其实实现并不困难,之所以耗费了大量的时间是因为一个小的语法错误在文本编辑中没有看出来,一个好的IDE对于代码繁冗的项目还是很必要的。想起之前做php开发的时候,一个bug一直没分析出来,后来试用了PhpStorm,打开项目后直接就自动分析出错误所在了。

说完了这个再说说WebsiteIP的实现机制,其实现中用到了LocalStorage来存放IP信息,而实际上是否使用LocalStorage关系不大,使用普通的对象存储也是可以的。关键就在于chrome.webRequest.onCompleted.addListener(function(details)),details包含了本次请求的各种信息,比如主机IP地址、状态码等等,对于诊断页面加载情况有一定帮助。

SVN递归忽略文件或目录

2015-03-20 • 技术文章

在SVN中忽略某个文件可以使用svn ps svn:ignore [filename|filelist] [dir]来实现,但是如果要实现一个文件列表的忽略最直观的方法是通过svn pe svn:ignore [dir]。在使用该命令时会遇到SVN编辑器未设置的提示,这时可以export SVN_EDITOR=vi指定vi或者其它编辑器来解决,为免今后再export也可以直接把export写进profile。

不过,我个人觉得不错的一个方案是创建一个.svnignore文件,在.svnignore文件里指定一个忽略列表,然后通过svn ps svn:ignore -F .svnignore [dir]来忽略比较好。如果有所有层忽略的需要,svn -R ps svn:ignore -F .svnignore .层层忽略,列表文件的目录不用加/直接递归忽略掉即可。

其实对于从仓库拉下来的工程,只要不手动添加不必要的文件来跟踪就没什么问题。如果使用IDEA等IDE,在设置中设定忽略项目也很方便。