2006-11-29

在 WEB 开发中指示文件的格式和显示方式

网络上的 WEB 服务器,往往网络地址的扩展名决定下载内容的格式。比如, http://www.google.com/images/logo_sm.gif 表示一个 gif 图片文件,http://www.ietf.org/rfc/rfc0001.txt 表示一个 txt 文本文件。然后,也有少数不是这样的,比如 Gmail ,链接地址都是看不懂的一串字符,可是这也能在浏览器中显示图片,并且对于附件中的图片,提供了直接在浏览器中查看和下载到本地两个按钮。这些功能都很人性化,是在返回的消息头信息中,告诉浏览器本消息格式这样的方式来实现的。
在返回的头信息中, Content-Type 字段指示了消息的类型,消息的类型由 MIME 决定。MIME ,全名 Multipurpose Internet Mail Extensions (多用途网际邮件扩展),是一个描述消息内容的标准,网络浏览器通过此标准解析收到的内容。
在 RFC( Request for Comments) 文档 rfc2045 中,对消息内容的类型作了以下说明:

content := "Content-Type" ":" type "/" subtype
           *(";" parameter)
           ; Matching of media type and subtype
           ; is ALWAYS case-insensitive.

type := discrete-type / composite-type

discrete-type := "text" / "image" / "audio" / "video" /
                 "application" / extension-token

composite-type := "message" / "multipart" / extension-token

extension-token := ietf-token / x-token

ietf-token := <An extension token defined by a
               standards-track RFC and registered
               with IANA.>

x-token := <The two characters "X-" or "x-" followed, with
            no intervening white space, by any token>

subtype := extension-token / iana-token

iana-token := <A publicly-defined extension token. Tokens
               of this form must be registered with IANA
               as specified in RFC 2048.>

parameter := attribute "=" value

attribute := token
             ; Matching of attributes
             ; is ALWAYS case-insensitive.

value := token / quoted-string

token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
            or tspecials>

tspecials := "(" / ")" / "<" / ">" / "@" /
             "," / ";" / ":" / "\" / <">
             "/" / "[" / "]" / "?" / "="
             ; Must be in quoted-string,
             ; to use within parameter values

上面指出,目前独立的内容类型主要有5种,分别是 text(文本),image(图片),audio(音频),video(视频),application(应用程序识别),当浏览器收到此类型定义时,解析器根据此字段调用不同的组件显示收到的消息。所以,修改 Content-Type ,我们可以指定浏览器以何种方式显示收到的消息。
同时,对于没有指定内容类型的消息, rfc 文档指定了默认值,如下:

Content-type: text/plain; charset=us-ascii

另外,在文档 rfc2183 中,补充了接收展现的信息,定义为 Content-Disposition :

disposition := "Content-Disposition" ":"
                disposition-type
                *(";" disposition-parm)

disposition-type := "inline"
                    / "attachment"
                    / extension-token
                    ; values are not case-sensitive

disposition-parm := filename-parm
                    / creation-date-parm
                    / modification-date-parm
                    / read-date-parm
                    / size-parm
                    / parameter

filename-parm := "filename" "=" value

creation-date-parm := "creation-date" "=" quoted-date-time

modification-date-parm := "modification-date" "=" quoted-date-time

read-date-parm := "read-date" "=" quoted-date-time

size-parm := "size" "=" 1*DIGIT

quoted-date-time := quoted-string
                    ; contents MUST be an RFC 822 `date-time'
                    ; numeric timezones (+HHMM or -HHMM) MUST be used

上面指定了展现时主要有两种类型: inline(直接打开)和 attachment(作为附件打开)。通过对 Content-Disposition 的修改,我们可以让浏览器直接显示或弹出保存文件的对话框。常用的附加参数还有文件名,创建日期,修改日期,文件大小,最后读取日期等,浏览器将遵循这些字段保存文件。
可以看到,浏览器通过对 Content-Type 和 Content-Disposition 的读取决定显示消息的类型和方式,与网络地址的扩展名毫无关系。下面分别使用 ASP.NET 和 Servlet 列出在客户端弹出图片下载窗口的方法。
使用 C# 在 ASP.NET 中的实现:

...
Response.ContentType = "image/gif";//指示客户端传输的内容为gif图片
Response.AddHeader("Content-Disposition",
     "attachment;filename=cs.gif");//默认的文件名是cs.gif
//在这里使用 Response.BinaryWrite 方法往客户端写二进制数据
Response.Close();
...

使用 Java 在 Servlet 中的实现:

...
resp.setContentType("image/gif");//指示客户端传输的内容为gif图片
resp.addHeader("Content-Disposition",
      "attachment;filename=java.gif");//默认的文件名是java.gif
//使用 resp.setContentLength 方法设定发送的长度
ServletOutputStream stream = resp.getOutputStream();
//在这里使用 stream.write 方法往客户端写二进制数据
stream.close();
...

2006-11-28

寒号鸟

哆罗罗,哆罗罗,寒风冻死我,明天就垒窝。

2006-11-26

美国大兵

172 Stryker 旅23步兵团的 Kyle Shelton 在摩苏尔搜寻武装分子

2006-11-24

一无所有

演唱:崔健

我曾经问个不休,你何时跟我走
可你却总是笑我,一无所有
我要给你我的追求,还有我的自由
可你却总是笑我,一无所有

噢……你何时跟我走
噢……你何时跟我走

脚下这地在走,身边那水在流
可你却总是笑我,一无所有
为何你总笑个没够,为何我总要追求
难道在你面前,我永远是一无所有

噢……你何时跟我走
噢……你何时跟我走

脚下这地在走,身边那水在流

告诉你我等了很久,告诉你我最后的要求
我要抓起你的双手,你这就跟我走
这时你的手在颤抖,这时你的泪在流
莫非你是正在告诉我,你爱我一无所有

噢……你这就跟我走
噢……你这就跟我走

噢……你这就跟我走

2006-11-23

旗木卡卡西

同样的忍术不能对我使用两次

今天我在人生的道路上迷失了

木叶第一技师复制忍者卡卡西

在 .Net 中使用低级键盘钩子屏蔽 Win 键

在微软的文档《HOW TO:在 Visual C# .NET 中设置窗口挂钩 》中,对 .NET 框架中的钩子有如下描述:

在 .NET 框架中不支持全局挂钩
您无法在 Microsoft .NET 框架中实现全局挂钩。若要安装全局挂钩,挂钩必须有一个本机动态链接库 (DLL) 导出以便将其本身插入到另一个需要调入一个有效而且一致的函数的进程中。这需要一个 DLL 导出,而 .NET 框架不支持这一点。托管代码没有让函数指针具有统一的值这一概念,因为这些函数是动态构建的代理。

在 .NET 2.0 中,已经可以开发程序使用全局挂钩了,下面给出使用 C# 开发低级键盘钩子屏蔽 Win 键的代码:
使用钩子需要用到 3 个 Win32 API 函数: SetWindowsHookEx,CallNextHookEx和UnhookWindowsHookEx,在 C# 中需要声明一下:

class Win32API
{
   [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
   public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

   [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
   public static extern int CallNextHookEx(IntPtr hhk, int nCode, uint wParam, int lParam);

   [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]
   public static extern int UnhookWindowsHookEx(IntPtr hhk);
}

低级键盘钩子用到了 KBDLLHOOKSTRUCT 结构,在 C# 中定义对应的结构:

struct KeyBoardHookStruct
{
   public UInt32 vkCode;
   public UInt32 scanCode;
   public UInt32 flags;
   public UInt32 time;
   public UInt32 dwExtraInfo;
}

对于低级键盘钩子,Win32 提供了 LowLevelKeyboardProc 格式的回调函数,对应的,在 C# 中使用代理:

delegate int HookProc(int nCode, uint wParam, int lParam);

最后封装一个类屏蔽左右 Win 键:

class MaskKey
{
   private const int WH_KEYBOARD_LL = 13;
   private const int HC_ACTION = 0;
   private const int VK_LWIN = 0x5B;
   private const int VK_RWIN = 0x5C;
   private IntPtr m_hook;

   public bool StartMaskKey()
   {
     if (m_hook != IntPtr.Zero) return false;

     IntPtr pInstance = Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().ManifestModule);
     m_hook = Win32API.SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, pInstance, 0);
     if (m_hook == IntPtr.Zero) return false;

     return true;
   }

   public bool StopMaskKey()
   {
     if (m_hook != IntPtr.Zero)
     {
       int succeed = Win32API.UnhookWindowsHookEx(m_hook);
       if (succeed == 0) return false;

       m_hook = IntPtr.Zero;
     }

     return true;
   }

   private int LowLevelKeyboardProc(int nCode, uint wParam, int lParam)
   {
     if (nCode == HC_ACTION)
     {
       KeyBoardHookStruct khs = (KeyBoardHookStruct)Marshal.PtrToStructure(
          new IntPtr(lParam), typeof(KeyBoardHookStruct));
       if (khs.vkCode == VK_LWIN || khs.vkCode == VK_RWIN) return 1;
     }
     return Win32API.CallNextHookEx(m_hook, nCode, wParam, lParam);
   }
}

上面的代码使用了低级键盘钩子,其中 ManifestModule 为 .Net 2.0 新增的属性,作用是获取包含当前程序集清单的模块。
编译成功后会发现程序仍然无法正确运行,这是因为需要修改 VS2005的编译选项,右键选择项目属性,在 Debug 选项卡中取消 Enable the Visual Studio hosting process 前面的选中标记, 重新编译运行,键盘的左右 Win 键已经不能使用了。

2006-11-20

美国反禁枪广告(四)

如果歹徒攻击这位女士会的到什么?她的身体、尊严和钱财?还是她准备好的子弹?易于攻击的受害者?还是武装的公民?让歹徒去猜吧!怎么能说“只有我失却了防卫能力,才能使他人免于暴力犯罪”呢?