前面的第一、第二部分已经对Wordpress中的十张表里的七张进行了说明,这里要介绍的是剩下的三张表的有关情况。 wp_posts: 用于保存你所有的文章(posts)的相关信息的表,非常的重要。一般来讲,它存储的数据是最多的。一共包括了21个字段。 ID – 每篇文章的唯一ID,bigint(20)值,附加属性auto_increment。 post_author – 每篇文章的作者的编号,int(4)值,应该对应的是wp_users.ID。 post_date – 每篇文章发表的时间,datetime值。它是GMT时间加上时区偏移量的结果。 post_date_gmt – 每篇文章发表时的GMT(格林威治)时间,datetime值。 post_content – 每篇文章的具体内容,longtext值。你在后台文章编辑页面中写入的所有内容都放在这里。 post_title – 文章的标题,text值。 post_category – 文章所属分类,int(4)值。 post_excerpt – 文章摘要,text值。 post_status – 文章当前的状态,枚举enum(’publish’,’draft’,’private’,’static’,’object’)值,publish为已 发表,draft为草稿,private为私人内容(不会被公开) ,static(不详),object(不详)。默认为publish。 comment_status – 评论设置的状态,也是枚举enum(’open’,’closed’,’registered_only’)值,open为允许评论,closed为不允 许评论,registered_only为只有注册用户方可评论。默认为open,即人人都可以评论。 ping_status – ping状态,枚举enum(’open’,’closed’)值,open指打开pingback功能,closed为关闭。默认值是open。 post_password – 文章密码,varchar(20)值。文章编辑才可为文章设定一个密码,凭这个密码才能对文章进行重新强加或修改。 post_name – 文章名,varchar(200)值。这通常是用在生成permalink时,标识某篇文章的一段文本或数字,也即post slug。 to_ping – 强制该文章去ping某个URI。text值。 pinged – 该文章被pingback的历史记录,text值,为一个个的URI。 post_modified – 文章最后修改的时间,datetime值,它是GMT时间加上时区偏移量的结果。 post_modified_gmt – 文章最后修改的GMT时间,datetime值。 post_content_filtered – 不详,text值。 post_parent – 文章的上级文章的ID,int(11)值,对应的是wp_posts.ID。默认为0,即没有上级文章。 guid – 这是每篇文章的一个地址,varchar(255)值。默认是这样的形式: http://your.blog.site/?p=1,如果你形成permalink功能,则通常会是: 你的Wordpress站点地址+文章名。 menu_order – 不详,int(11)值,默认为0。 post_type – 文章类型,具体不详,varchar(100)值。默认为0。 post_mime_type – 不详。varchar(100)值。 comment_count – 评论计数,具体用途不详,bigint(20)值。 wp_usermeta : 用于保存用户元信息(meta)的表,共4个字段: umeta_id – 元信息ID,bigint(20)值,附加属性auto_increment。 […]
View DetailsDataGridView控件 DataGridView是用于Windows Froms 2.0的新网格控件。它可以取代先前版本中DataGrid控件,它易于使用并高度可定制,支持很多我们的用户需要的特性。 关于本文档: 本文档不准备面面俱到地介绍DataGridView,而是着眼于深入地介绍一些技术点的高级特性。 本文档按逻辑分为5个章节,首先是结构和特性的概览,其次是内置的列/单元格类型的介绍,再次是数据操作相关的内容,然后是主要特性的综述,最后是最佳实践。 大部分章节含有一个“Q & A”部分,来回答该章节相关的一些常见问题。注意,某些问题会由于知识点的关联性重复出现在多个章节。这些问题、答案及其附带的示例代码都包含在本文档的附录部分。 内容 1 何为DataGridView.. 4 1.1 DataGridView和DataGrid 之间的区别… 4 1.2 DataGridView的亮点… 5 2 DataGridView的结构… 6 2.1 结构元素… 6 2.2 单元格和组… 6 2.3 DataGridView的单元格… 6 2.3.1 DataGridViewCell的工作机制… 7 2.4 DataGridView的列… 9 2.5 DataGridView的编辑控件… 9 2.6 DataGridView的行… 10 3 列/单元格类型揭密… 11 3.1 DataGridViewTextBoxColumn. 11 3.2 DataGridViewCheckBoxColumn. 12 3.3 DataGridViewImageColumn. 12 3.4 DataGridViewButtonColumn. 13 3.5 DataGridViewComboBoxColumn. 13 3.5.1 DataError与ComboBox列… 13 3.6 DataGridViewLinkColumn. 14 4 操作数据… 15 4.1 数据输入和验证的相关事件… 15 4.1.1 数据验证相关事件的顺序… 15 4.1.2 验证数据… 15 4.1.3 在新行中的数据输入… 16 4.2 关于Null值… 19 4.2.1 […]
View DetailsVisual Studio 2012 Ultimate旗舰版序列号: YKCW6-BPFPF-BT8C9-7DCTH-QXGWC RBCXF-CVBGR-382MK-DFHJ4-C69G8 YQ7PR-QTHDM-HCBCV-9GKGG-TB2TM 点击帮助(help)-注册产品(Register Product)-输入Key就可以了
View Details摘要:PHP开发技术在这几年依然比较火热,也有越来越多的开发者加入到了PHP开发阵营,在复杂的框架和冗余的代码面前,选择合适的PHP库就显得 尤为重要,优秀的PHP库可以为你节省很多代码和编码时间。 下面是一些非常有用的PHP类库,相信一定可以为你的WEB开发提供更好和更为快速的方法。 图表库 下面的类库可以让你很简的创建复杂的图表和图片。当然,它们需要GD库的支持。 pChart – 一个可以创建统计图的库。 Libchart – 这也是一个简单的统计图库。 JpGraph – 一个面向对象的图片创建类。 Open Flash Chart – 这是一个基于Flash的统计图。 RSS 解析 解释RSS并是一件很单调的事情,不过幸好你有下面的类库可以帮助你方便地读取RSS的Feed。 MagpieRSS – 开源的PHP版RSS解析器,据说功能强大,未验证。 SimplePie – 这是一个非常快速,而且易用的RSS和Atom 解析库。 缩略图生成 phpThumb – 功能很强大,如何强大还是自己去体会吧。 支付 你的网站需要处理支付方面的事情?需要一个和支付网关的程序?下面这个程序可以帮到你。 PHP Payment Library – 支持Paypal, Authorize.net 和2Checkout (2CO) OpenID PHP-OpenID – 支持OpenID的一个PHP库。OpenID是帮助你使用相同的用户名和口令登录不同的网站的一种解决方案。如果你对OpenID不熟悉的话,你可以到这里看看:http://openid.net.cn/ 数据为抽象/对象关系映射ORM ADOdb – 数据库抽象 Doctrine – 对象关系映射Object relational mapper (ORM) ,需要 PHP 5.2.3+ 版本,一个非常强大的database abstraction layer (DBAL). Propel – 对象关系映射框架- PHP5 Outlet – 也是关于对象关系映射的一个工具。 注:对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形 式。 这也同时暗示者额外的执行开销;然而,如果ORM作为一种中间件实现,则会有很多机会做优化,而这些在手写的持久层并不存在。 更重要的是用于控制转换的元数据需要提供和管理;但是同样,这些花费要比维护手写的方案要少;而且就算是遵守ODMG规范的对象数据库依然需要类级别的元 数据。 PDF 生成器 FPDF – 这量一个可以让你生成PDF的纯PHP类库。 Excel 相关 你的站点需要生成Excel?没有问题,下面这两个类库可以让你轻松做到这一点。 php-excel […]
View DetailsIf you are a PHP developer or graphic web designer you must be aware and know about the basic tools one requires by which to perform certain web related tasks. However, most experienced web developers and designers constantly look for the new tools which they can use to make their workflow and web related tasks easier, faster, and better. In this article we have collected a few of the best PHP Tools and Applications to assist you within your work related tasks. We hope you will find them […]
View DetailsPHP标准库 (SPL)的目的就是提供一组接口,让开发者在PHP5中充分利用面向对象编程。因此本文我们搜集了8个最好的,能辅助开发者简化他们的工作,为他们的开发任务服务的PHP库。 如果你喜欢本文,也许你对我们的其他文章感兴趣:8个给开发者的最好的PHP工具和应用 1. Whoops : 更好的php错误报告库 Whoops是一个易于处理和调试错误的PHP库 。它提供基于堆栈的的错误处理和好看的错误界面。它有个简单的API来处理异常,跟踪帧和数据,并能和任何框架整合(随时可用的集成端和Silex)。 Source 2. PhpFastCache phpFastCache 是一个开源的 PHP 缓存库,只提供一个简单的 PHP 文件,可方便集成到已有项目,支持多种缓存方法,包括:apc, memcache, memcached, wincache, files, pdo and mpdo。可通过简单的 API 来定义缓存的有效时间。 Source 3. Eden : 功能强大的 PHP 库 Eden是一个开源且免费的PHP快速开发类库。它包含很多组件用来自动加载、事件驱动、文档系统、缓存、模板、国际化、数据库、web服务、支付网关、装载和云服务技术。为了给我们最好的选择,他已经将现有的函数实现了与谷歌服务(Youtube, Drive, Contacts, Analytics, Checkout, Maps),Facebook(脸谱), Twitter(推特), Tumblr(轻博客), PayPal(贝宝), Authorize.net, FedEx(联邦快递), UPS(联合包裹服务公司), Amazon + Rackspace Clouds(亚马逊+Rackspace 云)等服务的交互。 Source 4. Php Error PHP Error 是一个开源的 PHP 库,用于转换标准的 PHP 错误信息,主要用于开发过程中的调试。PHP Error 紧密集成到 PHP 环境中,显示带语法高亮的错误提示。 Source 5. Detector Detector是一个开源的PHP类库用于检测关于用户的浏览器环境的许多东西。它可以获得浏览器的使用和浏览器的html5 css3功能,分析是否移动电话、平板电脑、桌面或网页爬虫和其他项如:颜色深度, 视口尺寸、cookie等支持。类库可以自动适应新的浏览器、版本和设备对每一个浏览器使用独特的用户代理字符。 Source 6. Opauth Opauth 是一个开源的 PHP 库,提供了 OAuth 认证的支持,让你无需关注不同 Provider 之间的差别,提供统一标准的访问方法。目前支持 Google、Twitter 和 Facebook,其他的 Provider 支持也将陆续提供。同时也支持处理任何 […]
View Details
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Data; using System.Data.Common; using System.Web.Script.Serialization; using System.Globalization; using System.Collections; using System.IO; using System.Drawing; using System.IO.Compression; using System.Runtime.Serialization.Formatters.Binary; namespace Pub.Class { public static class ArrayExtensions { public static bool IsNullEmpty(this Array array) { return array == null || array.Length == 0; } public static bool IsNullEmpty(this ArrayList list) { return (list == null) || (list.Count == 0); } public static int GetInArrayID(this string[] stringArray, string strSearch, bool caseInsensetive) { for (int i = 0; i < stringArray.Length; i++) { if (caseInsensetive) { if(strSearch.ToLower() == stringArray[i].ToLower()) return i; } else { if(strSearch == stringArray[i]) return i; } } return -1; } public static int GetInArrayID(this string[] stringArray, string strSearch) { return GetInArrayID(stringArray, strSearch, true); } public static string ToDelimitedString<T>(this T[] array, string format, string delimiter) { if (array == null || array.Length == 0) return string.Empty; if (format.IsNullEmpty()) format = "{0}"; StringBuilder builder = new StringBuilder(); for (int index = 0; index < array.Length; ++index) { if (index != 0) builder.Append(delimiter); builder.AppendFormat(format, array[index]); } return builder.ToString(); } public static T[] RemoveDuplicates<T>(this T[] array) { ArrayList al = new ArrayList(); for (int i = 0; i < array.Length; i++) { if (!al.Contains(array[i])) al.Add(array[i]); } return (T[])al.ToArray(typeof(T)); } public static T[] Slice<T>(this T[] array, int start, int end) { if (start >= array.Length) { start = 0; end = 0; } if (end < 0) end = array.Length - start - end; if (end <= start) end = start; if (end >= array.Length) end = array.Length-1; int len = end - start + 1; T[] res = new T[len]; for (int i = 0; i < len; i++) res[i] = array[i + start]; return res; } public static string Join<T>(this T[] array, string splitStr) { StringBuilder sb = new StringBuilder(); foreach(T info in array) { sb.AppendFormat("{0}{1}", info.ToString(), splitStr); } return sb.ToString().Left(sb.Length - splitStr.Length); } public static void Action<T>(this T[] inArray, Action<T, Int32> inAction) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++) { inAction(inArray[i0], i0); } } public static void Action<T>(this T[,] inArray, Action<T, Int32, Int32> inAction) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++) { for (int i1 = 0; i1 < inArray.GetLength(1); i1++) inAction(inArray[i0, i1], i0, i1); } } public static void Action<T>(this T[,,] inArray, Action<T, Int32, Int32, Int32> inAction) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++) { for (int i1 = 0; i1 < inArray.GetLength(1); i1++) { for (int i2 = 0; i2 < inArray.GetLength(2); i2++) inAction(inArray[i0, i1, i2], i0, i1, i2); } } } public static void Action<T>(this T[,] inArray, Int32 inDimension, Int32 inIndex, Action<T, Int32> inAction) { if (inDimension == 0) { for (int i = 0; i < inArray.GetLength(1); i++) inAction(inArray[inIndex, i], i); } else if (inDimension == 1) { for (int i = 0; i < inArray.GetLength(0); i++) inAction(inArray[i, inIndex], i); } else { throw new ArgumentException("inDimension must be zero or one"); } } public static void Action<T>(this T[,,] inArray, Int32 inDimension, Int32 inIndex, Action<T, Int32, Int32> inAction) { if (inDimension == 0) { for (int i0 = 0; i0 < inArray.GetLength(1); i0++) { for (int i1 = 0; i1 < inArray.GetLength(2); i1++) inAction(inArray[inIndex, i0, i1], i0, i1); } } else if (inDimension == 1) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++){ for (int i1 = 0; i1 < inArray.GetLength(2); i1++) inAction(inArray[i0, inIndex, i1], i0, i1); } } else if (inDimension == 2) { for (int i0 = 0; i0 < inArray.GetLength(0); i0++){ for (int i1 = 0; i1 < inArray.GetLength(1); i1++) inAction(inArray[i0, i1, inIndex], i0, i1); } } else { throw new ArgumentException("inDimension must be zero or one or two"); } } public static Image ToImage(this byte[] bytes) { if (bytes != null) { MemoryStream ms = new MemoryStream(bytes, 0, bytes.Length); ms.Write(bytes, 0, bytes.Length); return Image.FromStream(ms, true); } return null; } public static bool ToFile(this byte[] bytes, string fileName, FileMode fileMode) { bool returnValue = true; FileAccess fileAccess = FileAccess.ReadWrite; if (fileMode == FileMode.Append) fileAccess = FileAccess.Write; FileStream fs = new FileStream(fileName, fileMode, fileAccess); BinaryWriter bw = new BinaryWriter(fs); try { bw.Write(bytes); } catch (Exception) { returnValue = false; } finally { fs.Close(); bw.Close(); } return returnValue; } public static byte[] Compress(this byte[] data) { using (MemoryStream output = new MemoryStream()) { using (DeflateStream def = new DeflateStream(output, CompressionMode.Compress)) { def.Write(data, 0, data.Length); } return output.ToArray(); } } public static byte[] Decompress(this byte[] data) { using (MemoryStream input = new MemoryStream()) { input.Write(data, 0, data.Length); input.Position = 0; using (DeflateStream def = new DeflateStream(input, CompressionMode.Decompress)) { using (MemoryStream output = new MemoryStream()) { byte[] buff = new byte[64]; int read = -1; read = def.Read(buff, 0, buff.Length); while (read > 0) { output.Write(buff, 0, read); read = def.Read(buff, 0, buff.Length); } def.Close(); return output.ToArray(); } } } } public static T Decompress<T>(this byte[] compressedData) where T : class { return compressedData.Decompress().Deserialize<T>(); } public static T Deserialize<T>(this byte[] data) where T : class { var formatter = new BinaryFormatter(); using (MemoryStream ms = new MemoryStream(data)) return formatter.Deserialize(ms) as T; } public static bool IsInArray(this string[] stringArray, string strSearch, bool caseInsensetive) { return stringArray.GetInArrayID(strSearch, caseInsensetive) >= 0; } public static bool IsInArray(this string[] stringarray, string str) { return stringarray.IsInArray(str, false); } public static bool IsInIPArray(this string[] iparray, string ip) { string[] userip = ip.Split(@"."); for (int ipIndex = 0; ipIndex < iparray.Length; ipIndex++) { string[] tmpip = iparray[ipIndex].Split(@"."); int r = 0; for (int i = 0; i < tmpip.Length; i++) { if (tmpip[i] == "*") return true; if (userip.Length > i) { if (tmpip[i] == userip[i]) r ++; else break; } else break; } if (r == 4) return true; } return false; } //Action<string, int> MsgW3 = (s, i) => { Msg.Write(s + i); Msg.Write("<br />"); }; //"test1,test2,test3".Split(',').Action<string>(MsgW3); } } |
转自:http://www.cnblogs.com/livexy/archive/2010/07/06/1772502.html
View Details上一篇专栏简单介绍了Connect模块的基本架构,它的执行模型十分简单,中间件机制也使得它十分易于扩展,具备良好的可伸缩性。在Connect的良好机制下,我们本章开始将逐步解开Connect生态圈中中间件部分,这部分给予Connect良好的功能扩展。 静态文件中间件 也许你还记得我曾经写过的Node.js静态文件服务器实战,那篇文章中我叙述了如何利用Node.js实现一个静态文件服务器的许多技术细节,包括路由实现,MIME,缓存控制,传输压缩,安全、欢迎页、断点续传等。但是这里我们不需要去亲自处理细节,Connect的static中间件为我们提供上述所有功能。代码只需寥寥3行即可:
1 2 3 |
var connect = require('connect'); var app = connect(); app.use(connect.static(__dirname + '/public')); |
在项目中需要临时搭建静态服务器,也无需安装apache之类的服务器,通过NPM安装Connect之后,三行代码即可解决需求。这里需要提及的是在使用该模块的一点性能相关的细节。 动静分离 前一章提及,app.use()方法在没有指定路由信息时,相当于app.use("/", middleware)。这意味着静态文件中间件将会在处理所有路径的请求。在动静态请求混杂的场景下,静态中间件会在动态请求时也调用fs.stat来检测文件系统是否存在静态文件。这造成了不必要的系统调用,使得性能降低。 解决影响性能的方法既是动静分离。利用路由检测,避免不必要的系统调用,可以有效降低对动态请求的性能影响。
1 |
app.use('/public', connect.static(__dirname + '/public')); |
在大型的应用中,动静分离通常无需到一个Node.js实例中进行,CDN的方式直接在域名上将请求分离。小型应用中,适当的进行动静分离即可避免不必要的性能损耗。 缓存策略 缓存策略包含客户端和服务端两个部分。客户端的缓存,主要是利用浏览器对HTTP协议响应头中cache-control和expires字段的支持。浏览器在得到明确的相应头后,会将文件缓存在本地,依据cache-control和expires的值进行相应的过期策略。这使得重复访问的过程中,浏览器可以从本地缓存中读取文件,而无需从网络读取文件,提升加载速度,也可以降低对服务器的压力。默认情况下静态中间件的最大缓存时设置为0,意味着它在浏览器关闭后就被清除。这显然不是我们所期望的结果。除非是在开发环境可以无视maxAge的设置外,生产环境请务必设置缓存,因为它能有效节省网络带宽。
1 |
app.use('/public', connect.static(__dirname + '/public', {maxAge: 86400000})); |
maxAge选项的单位为毫秒。YUI3的CDN服务器设置过期时间为10年,是一个值得参考的值。静态文件如果在客户端被缓存,在需要清除缓存的时候,又该如何清除呢?这里的实现方法较多,一种较为推荐的做法是为文件进行md5处理。
1 |
http://some.url/some.js?md5 |
当文件内容产生改变时,md5值也将发生改变,浏览器根据URL的不同会重新获取静态文件。md5的方式可以避免不必要的缓存清除,也能精确清除缓存。由于浏览器本身缓存容量的限制,尽管我们可能设置了10年的过期时间,但是也许两天之后就被新的静态文件挤出了本地缓存。这将持续引起静态服务器的响应,也即意味着,客户端缓存并不能完全解决降低服务器压力的问题。为了解决静态服务器重复读取磁盘造成的压力,这里需要引出第二个相关的中间件:staticCache。
1 2 |
app.use(connect.staticCache()); app.use(“/public”, connect.static(__dirname + '/public', {maxAge: 86400000})); |
这是一个提供上层缓存功能的中间件,能够将磁盘中的文件加载到内存中,以提高响应速度和提高性能。它的官方测试数据如下:
1 2 3 |
static(): 2700 rps node-static: 5300 rps static() + staticCache(): 7500 rps |
另一个专门用于静态文件托管的模块叫node-static,其性能是Connect静态文件中间件的效率的两倍。但是在缓存中间件的协助下,可以弥补性能损失。事实上,这个中间件在生产环境下并不推荐被使用,而且它将在Connect 3.0版本中被移除。但是它的实现中有值得玩味的地方,这有助于我们认识Node.js模型的优缺点。staticCache中间件有两个主要的选项:maxObjects和maxLength。代表的是能存储多少个文件和单个文件的最大尺寸,其默认值为128和256kb。为何会有这两个选项的设定,原因在于V8有内存限制的原因,作为缓存,如果没有良好的过期策略,缓存将会无限增加,直到内存溢出。设置存储数量和单个文件大小后,可以有效抑制缓存区的大小。事实上,该缓存还存在的缺陷是单机情况下,通常为了有效利用CPU,Node.js实例并不只有一个,多个实例进程之间将会存在冗余的缓存占用,这对于内存使用而言是浪费的。除此之外,V8的垃圾回收机制是暂停JavaScript线程执行,通过扫描的方式决定是否回收对象。如果缓存对象过大,键太多,则扫描的时间会增加,会引起JavaScript响应业务逻辑的速度变慢。但是这个模块并非没有存在的意义,上述提及的缺陷大多都是V8内存限制和Node.js单线程的原因。解决该问题的方式则变得明了。风险转移是Node.js中常用于解决资源不足问题的方式,尤其是内存方面的问题。将缓存点,从Node.js实例进程中转移到第三方成熟的缓存中去即可。这可以保证: 缓存内容不冗余。 集中式缓存,减少不一致性的发生。 缓存的算法更优秀以保持较高的命中率。 让Node.js保持轻量,以解决它更擅长的问题。 Connect推荐服务器端缓存采用varnish这样的成熟缓存代理。而笔者目前的项目则是通过Redis来完成后端缓存的任务。 参考内容 https://www.varnish-cache.org/releases http://www.senchalabs.org/connect/static.html http://www.senchalabs.org/connect/staticCache.html 转自:http://www.infoq.com/cn/articles/nodejs-8-connect-module-part-2
View DetailsConnect模块背景 Node.js的愿望是成为一个能构建高速,可伸缩的网络应用的平台,它本身具有基于事件,异步,非阻塞,回调等特性,这在前几篇专栏中有过描述。正是基于这样的一些特性,Node.js平台上的Web框架也具有不同于其他平台的一些特性,其中Connect是众多Web框架中的佼佼者。Connect在它的官方介绍中,它是Node的一个中间件框架。超过18个捆绑的中间件和一些精选第三方中间件。尽管Connect可能不是性能最好的Node.jsWeb框架,但它却几乎是最为流行的Web框架。为何Connect能在众多框架中胜出,其原因不外乎有如下几个: 模型简单 中间件易于组合和插拔 中间件易于定制和优化 丰富的中间件 Connect自身十分简单,其作用是基于Web服务器做中间件管理。至于如何如何处理网络请求,这些任务通过路由分派给管理的中间件们进行处理。它的处理模型仅仅只是一个中间队列,进行流式处理而已,流式处理可能性能不是最优,但是却是最易于被理解和接受。基于中间件可以自由组合和插拔的情况,优化它十分容易。Connect模块目前在NPM仓库的MDO(被依赖最多的模块)排行第八位。但这并没有真实反映出它的价值,因为排行第五位的Express框架实际上是依赖Connect创建而成的。关于Express的介绍,将会在后续的专栏中一一为你讲解。 中间件 让我们回顾一下Node.js最简单的Web服务器是如何编写的:
1 2 3 4 5 |
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); |
我们从最朴素的Web服务器处理流程开始,可以看到HTTP模块基于事件处理网络访问无外乎两个主要的因素,请求和响应。同理的是Connect的中间件也是扮演这样一个角色,处理请求,然后响应客户端或是让下一个中间件继续处理。如下是一个中间件最朴素的原型:
1 2 3 |
function (req, res, next) { // 中间件 } |
在中间件的上下文中,有着三个变量。分别代表请求对象、响应对象、下一个中间件。如果当前中间件调用了res.end()结束了响应,执行下一个中间件就显得没有必要。 流式处理 为了演示中间件的流式处理,我们可以看看中间件的使用形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var app = connect(); // Middleware app.use(connect.staticCache()); app.use(connect.static(__dirname + '/public')); app.use(connect.cookieParser()); app.use(connect.session()); app.use(connect.query()); app.use(connect.bodyParser()); app.use(connect.csrf()); app.use(function (req, res, next) { // 中间件 }); app.listen(3001); |
Conncet提供use方法用于注册中间件到一个Connect对象的队列中,我们称该队列叫做中间件队列。 Conncet的部分核心代码如下,它通过use方法来维护一个中间件队列。然后在请求来临的时候,依次调用队列中的中间件,直到某个中间件不再调用下一个中间件为止。
1 2 3 4 5 6 7 8 9 10 |
app.stack = []; app.use = function(route, fn){ // … // add the middleware debug('use %s %s', route || '/', fn.name || 'anonymous'); this.stack.push({ route: route, handle: fn }); return this; }; |
值得注意的是,必须要有一个中间件调用res.end()方法来告知客户端请求已被处理完成,否则客户端将一直处于等待状态。流式处理也是Node.js中用于流程控制的经典模式,Connect模块是典型的应用了它。流式处理的好处在于,每一个中间层的职责都是单一的,开发者通过这个模式可以将复杂的业务逻辑进行分解。 路由 从前文可以看到其实app.use()方法接受两个参数,route和fn,既路由信息和中间件函数,一个完整的中间件,其实包含路由信息和中间件函数。路由信息的作用是过滤不匹配的URL。请求在遇见路由信息不匹配时,直接传递给下一个中间件处理。通常在调用app.use()注册中间件时,只需要传递一个中间件函数即可。实际上这个过程中,Connect会将/作为该中间件的默认路由,它表示所有的请求都会被该中间件处理。中间件的优势类似于Java中的过滤器,能够全局性地处理一些事务,使得业务逻辑保持简单。任何事物均有两面性,当你调用app.use()添加中间件的时候,需要考虑的是中间件队列是否太长,因为每一层中间件的调用都是会降低性能的。为了提高性能,在添加中间件的时候,如非全局需求的,尽量附带上精确的路由信息。以multipart中间件为例,它用于处理表单提交的文件信息,相对而言较为耗费资源。它存在潜在的问题,那就是有可能被人在客户端恶意提交文件,造成服务器资源的浪费。如果不采用路由信息加以限制,那么任何URL都可以被攻击。
1 |
app.use("/upload", connect.multipart({ uploadDir: path })); |
加上精确的路由信息后,可以将问题减小。 MVC目录 借助Connect可以自由定制中间件的优势,可以自行提升性能或是设计出适合自己需要的项目。Connect自身提供了路由功能,在此基础上,可以轻松搭建MVC模式的框架,以达到开发效率和执行效率的平衡。以下是笔者项目中采用的目录结构,清晰地划分目录结构可以帮助划分代码的职责,此处仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
├── Makefile // 构建文件,通常用于启动单元测试运行等操作 ├── app.js // 应用文件 ├── automation // 自动化测试目录 ├── bin // 存放启动应用相关脚本的目录 ├── conf // 配置文件目录 ├── controllers // 控制层目录 ├── helpers // 帮助类库 ├── middlewares // 自定义中间件目录 ├── models // 数据层目录 ├── node_modules // 第三方模块目录 ├── package.json // 项目包描述文件 ├── public // 静态文件目录 │ ├── images // 图片目录 │ ├── libs // 第三方前端JavaScript库目录 │ ├── scripts // 前端JavaScript脚本目录 │ └── styles // 样式表目录 ├── test // 单元测试目录 └── views // 视图层目录 |
参考: Connect主页 http://www.senchalabs.org/connect/ NPM仓库 http://search.npmjs.org/ 转自:http://www.infoq.com/cn/articles/nodejs-connect-module
View Details作为前端的JSer,是一件非常幸福的事情,因为在字符串上从来没有出现过任何纠结的问题。我们来看看PHP对字符串长度的判断结果:
1 2 3 4 5 |
<? php echo strlen("0123456789"); echo strlen("零一二三四五六七八九"); echo mb_strlen("零一二三四五六七八九", "utf-8"); echo "\n"; |
以上三行判断分别返回10、30、10。对于中国人而言,strlen这个方法对于Unicode的判断结果是非常让人疑惑。而看看JavaScript中对字符串长度的判断,就知道这个length属性对调用者而言是多么友好。
1 2 3 |
console.log("0123456789".length); // 10 console.log("零一二三四五六七八九".length); /10 console.log("\u00bd".length); // 1 |
尽管在计算机内部,一个中文字和一个英文字占用的字节位数是不同的,但对于用户而言,它们拥有相同的长度。我认为这是JavaScript中 String处理得精彩的一个点。正是由于这个原因,所有的数据从后端传输到前端被调用时,都是这般友好的字符串。所以对于前端工程师而言,他们是没有字 符串Buffer的概念的。如果你是一名前端工程师,那么从此在与Node.js打交道的过程中,一定要小心Buffer啦,因为它比传统的String 要调皮一点。 你该小心Buffer啦 像许多计算机的技术一样,都是从国外传播过来的。那些以英文作为母语的传道者们应该没有考虑过英文以外的使用者,所以你有可能看到如下这样一段代码在向你描述如何在data事件中连接字符串。
1 2 3 4 5 6 7 8 9 |
var fs = require('fs'); var rs = fs.createReadStream('testdata.md'); var data = ''; rs.on("data", function (trunk){ data += trunk; }); rs.on("end", function () { console.log(data); }); |
如果这个文件读取流读取的是一个纯英文的文件,这段代码是能够正常输出的。但是如果我们再改变一下条件,将每次读取的buffer大小变成一个奇数,以模拟一个字符被分配在两个trunk中的场景。
1 |
var rs = fs.createReadStream('testdata.md', {bufferSize: 11}); |
我们将会得到以下这样的乱码输出:
1 |
事件循���和请求���象构成了Node.js���异步I/O模型的���个基本���素,这也是典���的消费���生产者场景。 |
造成这个问题的根源在于data += trunk语句里隐藏的错误,在默认的情况下,trunk是一个Buffer对象。这句话的实质是隐藏了toString的变换的:
1 |
data = data.toString() + trunk.toString(); |
由于汉字不是用一个字节来存储的,导致有被截破的汉字的存在,于是出现乱码。解决这个问题有一个简单的方案,是设置编码集:
1 |
var rs = fs.createReadStream('testdata.md', {encoding: 'utf-8', bufferSize: 11}); |
这将得到一个正常的字符串响应:
1 |
事件循环和请求对象构成了Node.js的异步I/O模型的两个基本元素,这也是典型的消费者生产者场景。 |
遗憾的是目前Node.js仅支持hex、utf8、ascii、binary、base64、ucs2几种编码的转换。对于那些因为历史遗留问题依旧还生存着的GBK,GB2312等编码,该方法是无能为力的。 有趣的string_decoder 在这个例子中,如果仔细观察,会发现一件有趣的事情发生在设置编码集之后。我们提到data += trunk等价于data = data.toString() + trunk.toString()。通过以下的代码可以测试到一个汉字占用三个字节,而我们按11个字节来截取trunk的话,依旧会存在一个汉字被分割在两个trunk中的情景。
1 2 |
console.log("事件循环和请求对象".length); console.log(new Buffer("事件循环和请求对象").length); |
按照猜想的toString()方式,应该返回的是事件循xxx和请求xxx象才对,其中“环”字应该变成乱码才对,但是在设置了encoding(默认的utf8)之后,结果却正常显示了,这个结果十分有趣。 在好奇心的驱使下可以探查到data事件调用了string_decoder来进行编码补足的行为。通过string_decoder对象输出第一个截取Buffer(事件循xx)时,只返回事件循这个字符串,保留xx。第二次通过string_decoder对象输出时检测到上次保留的xx,将上次剩余内容和本次的Buffer进行重新拼接输出。于是达到正常输出的目的。 string_decoder,目前在文件流读取和网络流读取中都有应用到,一定程度上避免了粗鲁拼接trunk导致的乱码错误。但是,遗憾在于string_decoder目前只支持utf8编码。它的思路其实还可以扩展到其他编码上,只是最终是否会支持目前尚不可得知。 连接Buffer对象的正确方法 那么万能的适应各种编码而且正确的拼接Buffer对象的方法是什么呢?我们从Node.js在github上的源码中找出这样一段正确读取文件,并连接buffer对象的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var buffers = []; var nread = 0; readStream.on('data', function (chunk) { buffers.push(chunk); nread += chunk.length; }); readStream.on('end', function () { var buffer = null; switch(buffers.length) { case 0: buffer = new Buffer(0); break; case 1: buffer = buffers[0]; break; default: buffer = new Buffer(nread); for (var i = 0, pos = 0, l = buffers.length; i < l; i++) { var chunk = buffers[i]; chunk.copy(buffer, pos); pos += chunk.length; } break; } }); |
在end事件中通过细腻的连接方式,最后拿到理想的Buffer对象。这时候无论是在支持的编码之间转换,还是在不支持的编码之间转换(利用iconv模块转换),都不会导致乱码。 简化连接Buffer对象的过程 上述一大段代码仅只完成了一件事情,就是连接多个Buffer对象,而这种场景需求将会在多个地方发生,所以,采用一种更优雅的方式来完成该过程是必要的。笔者基于以上的代码封装出一个bufferhelper模块,用于更简洁地处理Buffer对象。可以通过NPM进行安装:
1 |
npm install bufferhelper |
下面的例子演示了如何调用这个模块。与传统data += trunk之间只是bufferHelper.concat(chunk)的差别,既避免了错误的出现,又使得代码可以得到简化而有效地编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var http = require('http'); var BufferHelper = require('bufferhelper'); http.createServer(function (request, response) { var bufferHelper = new BufferHelper(); request.on("data", function (chunk) { bufferHelper.concat(chunk); }); request.on('end', function () { var html = bufferHelper.toBuffer().toString(); response.writeHead(200); response.end(html); }); }).listen(8001); |
所以关于Buffer对象的操作的最佳实践是: 保持编码不变,以利于后续编码转换 使用封装方法达到简洁代码的目的 参考 https://github.com/joyent/node/blob/master/lib/fs.js#L107 https://github.com/JacksonTian/bufferhelper 转自:http://www.infoq.com/cn/articles/nodejs-about-buffer
View Details