paulwong

#

Netty3架构解析

前记

很早以前就有读Netty源码的打算了,然而第一次尝试的时候从Netty4开始,一直抓不到核心的框架流程,后来因为其他事情忙着就放下了。这次趁着休假重新捡起这个硬骨头,因为Netty3现在还在被很多项目使用,因而这次决定先从Netty3入手,瞬间发现Netty3的代码比Netty4中规中矩的多,很多概念在代码本身中都有清晰的表达,所以半天就把整个框架的骨架搞清楚了。再读Netty4对Netty3的改进总结,回去读Netty4的源码,反而觉得轻松了,一种豁然开朗的感觉。

记得去年读Jetty源码的时候,因为代码太庞大,并且自己的HTTP Server的了解太少,因而只能自底向上的一个一个模块的叠加,直到最后把所以的模块连接在一起而看清它的真正核心骨架。现在读源码,开始习惯先把骨架理清,然后延伸到不同的器官、血肉而看清整个人体。

本文从Reactor模式在Netty3中的应用,引出Netty3的整体架构以及控制流程;然而除了Reactor模式,Netty3还在ChannelPipeline中使用了Intercepting Filter模式,这个模式也在Servlet的Filter中成功使用,因而本文还会从Intercepting Filter模式出发详细介绍ChannelPipeline的设计理念。本文假设读者已经对Netty有一定的了解,因而不会包含过多入门介绍,以及帮Netty做宣传的文字。

Netty3中的Reactor模式

Reactor模式在Netty中应用非常成功,因而它也是在Netty中受大肆宣传的模式,关于Reactor模式可以详细参考本人的另一篇文章《Reactor模式详解》,对Reactor模式的实现是Netty3的基本骨架,因而本小节会详细介绍Reactor模式如何应用Netty3中。

如果读《Reactor模式详解》,我们知道Reactor模式由Handle、Synchronous Event Demultiplexer、Initiation Dispatcher、Event Handler、Concrete Event Handler构成,在Java的实现版本中,Channel对应Handle,Selector对应Synchronous Event Demultiplexer,并且Netty3还使用了两层Reactor:Main Reactor用于处理Client的连接请求,Sub Reactor用于处理和Client连接后的读写请求(关于这个概念还可以参考Doug Lea的这篇PPT:Scalable IO In Java)。所以我们先要解决Netty3中使用什么类实现所有的上述模块并把他们联系在一起的,以NIO实现方式为例:

模式是一种抽象,但是在实现中,经常会因为语言特性、框架和性能需要而做一些改变,因而Netty3对Reactor模式的实现有一套自己的设计:

1. ChannelEvent:Reactor是基于事件编程的,因而在Netty3中使用ChannelEvent抽象的表达Netty3内部可以产生的各种事件,所有这些事件对象在Channels帮助类中产生,并且由它将事件推入到ChannelPipeline中,ChannelPipeline构建ChannelHandler管道,ChannelEvent流经这个管道实现所有的业务逻辑处理。ChannelEvent对应的事件有:ChannelStateEvent表示Channel状态的变化事件,而如果当前Channel存在Parent Channel,则该事件还会传递到Parent Channel的ChannelPipeline中,如OPEN、BOUND、CONNECTED、INTEREST_OPS等,该事件可以在各种不同实现的Channel、ChannelSink中产生;MessageEvent表示从Socket中读取数据完成、需要向Socket写数据或ChannelHandler对当前Message解析(如Decoder、Encoder)后触发的事件,它由NioWorker、需要对Message做进一步处理的ChannelHandler产生;WriteCompletionEvent表示写完成而触发的事件,它由NioWorker产生;ExceptionEvent表示在处理过程中出现的Exception,它可以发生在各个构件中,如Channel、ChannelSink、NioWorker、ChannelHandler中;IdleStateEvent由IdleStateHandler触发,这也是一个ChannelEvent可以无缝扩展的例子。注:在Netty4后,已经没有ChannelEvent类,所有不同事件都用对应方法表达,这也意味这ChannelEvent不可扩展,Netty4采用在ChannelInboundHandler中加入userEventTriggered()方法来实现这种扩展,具体可以参考这里

2. ChannelHandler:在Netty3中,ChannelHandler用于表示Reactor模式中的EventHandler。ChannelHandler只是一个标记接口,它有两个子接口:ChannelDownstreamHandler和ChannelUpstreamHandler,其中ChannelDownstreamHandler表示从用户应用程序流向Netty3内部直到向Socket写数据的管道,在Netty4中改名为ChannelOutboundHandler;ChannelUpstreamHandler表示数据从Socket进入Netty3内部向用户应用程序做数据处理的管道,在Netty4中改名为ChannelInboundHandler。

3. ChannelPipeline:用于管理ChannelHandler的管道,每个Channel一个ChannelPipeline实例,可以运行过程中动态的向这个管道中添加、删除ChannelHandler(由于实现的限制,在最末端的ChannelHandler向后添加或删除ChannelHandler不一定在当前执行流程中起效,参考这里)。ChannelPipeline内部维护一个ChannelHandler的双向链表,它以Upstream(Inbound)方向为正向,Downstream(Outbound)方向为方向。ChannelPipeline采用Intercepting Filter模式实现,具体可以参考这里,这个模式的实现在后一节中还是详细介绍。

4. NioSelector:Netty3使用NioSelector来存放Selector(Synchronous Event Demultiplexer),每个新产生的NIO Channel都向这个Selector注册自己以让这个Selector监听这个NIO Channel中发生的事件,当事件发生时,调用帮助类Channels中的方法生成ChannelEvent实例,将该事件发送到这个Netty Channel对应的ChannelPipeline中,而交给各级ChannelHandler处理。其中在向Selector注册NIO Channel时,Netty Channel实例以Attachment的形式传入,该Netty Channel在其内部的NIO Channel事件发生时,会以Attachment的形式存在于SelectionKey中,因而每个事件可以直接从这个Attachment中获取相关链的Netty Channel,并从Netty Channel中获取与之相关联的ChannelPipeline,这个实现和Doug Lea的Scalable IO In Java一模一样。另外Netty3还采用了Scalable IO In Java中相同的Main Reactor和Sub Reactor设计,其中NioSelector的两个实现:Boss即为Main Reactor,NioWorker为Sub Reactor。Boss用来处理新连接加入的事件,NioWorker用来处理各个连接对Socket的读写事件,其中Boss通过NioWorkerPool获取NioWorker实例,Netty3模式使用RoundRobin方式放回NioWorker实例。更形象一点的,可以通过Scalable IO In Java的这张图表达:


若与Ractor模式对应,NioSelector中包含了Synchronous Event Demultiplexer,而ChannelPipeline中管理着所有EventHandler,因而NioSelector和ChannelPipeline共同构成了Initiation Dispatcher。

5. ChannelSink:在ChannelHandler处理完成所有逻辑需要向客户端写响应数据时,一般会调用Netty Channel中的write方法,然而在这个write方法实现中,它不是直接向其内部的Socket写数据,而是交给Channels帮助类,内部创建DownstreamMessageEvent,反向从ChannelPipeline的管道中流过去,直到第一个ChannelHandler处理完毕,最后交给ChannelSink处理,以避免阻塞写而影响程序的吞吐量。ChannelSink将这个MessageEvent提交给Netty Channel中的writeBufferQueue,最后NioWorker会等到这个NIO Channel已经可以处理写事件时无阻塞的向这个NIO Channel写数据。这就是上图的send是从SubReactor直接出发的原因。

6. Channel:Netty有自己的Channel抽象,它是一个资源的容器,包含了所有一个连接涉及到的所有资源的饮用,如封装NIO Channel、ChannelPipeline、Boss、NioWorkerPool等。另外它还提供了向内部NIO Channel写响应数据的接口write、连接/绑定到某个地址的connect/bind接口等,个人感觉虽然对Channel本身来说,因为它封装了NIO Channel,因而这些接口定义在这里是合理的,但是如果考虑到Netty的架构,它的Channel只是一个资源容器,有这个Channel实例就可以得到和它相关的基本所有资源,因而这种write、connect、bind动作不应该再由它负责,而是应该由其他类来负责,比如在Netty4中就在ChannelHandlerContext添加了write方法,虽然netty4并没有删除Channel中的write接口。

Netty3中的Intercepting Filter模式

如果说Reactor模式是Netty3的骨架,那么Intercepting Filter模式则是Netty的中枢。Reactor模式主要应用在Netty3的内部实现,它是Netty3具有良好性能的基础,而Intercepting Filter模式则是ChannelHandler组合实现一个应用程序逻辑的基础,只有很好的理解了这个模式才能使用好Netty,甚至能得心应手。

关于Intercepting Filter模式的详细介绍可以参考这里,本节主要介绍Netty3中对Intercepting Filter模式的实现,其实就是DefaultChannelPipeline对Intercepting Filter模式的实现。在上文有提到Netty3的ChannelPipeline是ChannelHandler的容器,用于存储与管理ChannelHandler,同时它在Netty3中也起到桥梁的作用,即它是连接Netty3内部到所有ChannelHandler的桥梁。作为ChannelPipeline的实现者DefaultChannelPipeline,它使用一个ChannelHandler的双向链表来存储,以DefaultChannelPipelineContext作为节点:

public interface ChannelHandlerContext {
    Channel getChannel();
    ChannelPipeline getPipeline();
    String getName();
    ChannelHandler getHandler();
    boolean canHandleUpstream();
    boolean canHandleDownstream();
    void sendUpstream(ChannelEvent e);
    void sendDownstream(ChannelEvent e);
    Object getAttachment();
    void setAttachment(Object attachment);
}

private final class DefaultChannelHandlerContext implements ChannelHandlerContext {
    volatile DefaultChannelHandlerContext next;
    volatile DefaultChannelHandlerContext prev;
    private final String name;
    private final ChannelHandler handler;
    private final boolean canHandleUpstream;
    private final boolean canHandleDownstream;
    private volatile Object attachment;
..
}

在DefaultChannelPipeline中,它存储了和当前ChannelPipeline相关联的Channel、ChannelSink以及ChannelHandler链表的head、tail,所有ChannelEvent通过sendUpstream、sendDownstream为入口流经整个链表:

public class DefaultChannelPipeline implements ChannelPipeline {
    private volatile Channel channel;
    private volatile ChannelSink sink;
    private volatile DefaultChannelHandlerContext head;
    private volatile DefaultChannelHandlerContext tail;

    public void sendUpstream(ChannelEvent e) {
        DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);
        if (head == null) {
            return;
        }
        sendUpstream(head, e);
    }

    void sendUpstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
        try {
            ((ChannelUpstreamHandler) ctx.getHandler()).handleUpstream(ctx, e);
        } catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    }

    public void sendDownstream(ChannelEvent e) {
        DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);
        if (tail == null) {
            try {
                getSink().eventSunk(this, e);
                return;
            } catch (Throwable t) {
                notifyHandlerException(e, t);
                return;
            }
        }
        sendDownstream(tail, e);
    }

    void sendDownstream(DefaultChannelHandlerContext ctx, ChannelEvent e) {
        if (e instanceof UpstreamMessageEvent) {
            throw new IllegalArgumentException("cannot send an upstream event to downstream");
        }
        try {
            ((ChannelDownstreamHandler) ctx.getHandler()).handleDownstream(ctx, e);
        } catch (Throwable t) {
            e.getFuture().setFailure(t);
            notifyHandlerException(e, t);
        }
    }

对Upstream事件,向后找到所有实现了ChannelUpstreamHandler接口的ChannelHandler组成链(
getActualUpstreamContext()),而对Downstream事件,向前找到所有实现了ChannelDownstreamHandler接口的ChannelHandler组成链(getActualDownstreamContext()):

    private DefaultChannelHandlerContext getActualUpstreamContext(DefaultChannelHandlerContext ctx) {
        if (ctx == null) {
            return null;
        }
        DefaultChannelHandlerContext realCtx = ctx;
        while (!realCtx.canHandleUpstream()) {
            realCtx = realCtx.next;
            if (realCtx == null) {
                return null;
            }
        }
        return realCtx;
    }
    private DefaultChannelHandlerContext getActualDownstreamContext(DefaultChannelHandlerContext ctx) {
        if (ctx == null) {
            return null;
        }
        DefaultChannelHandlerContext realCtx = ctx;
        while (!realCtx.canHandleDownstream()) {
            realCtx = realCtx.prev;
            if (realCtx == null) {
                return null;
            }
        }
        return realCtx;
    }

在实际实现ChannelUpstreamHandler或ChannelDownstreamHandler时,调用 ChannelHandlerContext中的sendUpstream或sendDownstream方法将控制流程交给下一个 ChannelUpstreamHandler或下一个ChannelDownstreamHandler,或调用Channel中的write方法发送 响应消息。

public class MyChannelUpstreamHandler implements ChannelUpstreamHandler {
    public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        // handle current logic, use Channel to write response if needed.
        
// ctx.getChannel().write(message);
        ctx.sendUpstream(e);
    }
}

public class MyChannelDownstreamHandler implements ChannelDownstreamHandler {
    public void handleDownstream(
            ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
        // handle current logic
        ctx.sendDownstream(e);
    }
}

当ChannelHandler向ChannelPipelineContext发送事件时,其内部从当前ChannelPipelineContext节点出发找到下一个ChannelUpstreamHandler或ChannelDownstreamHandler实例,并向其发送ChannelEvent,对于Downstream链,如果到达链尾,则将ChannelEvent发送给ChannelSink:

public void sendDownstream(ChannelEvent e) {
    DefaultChannelHandlerContext prev = getActualDownstreamContext(this.prev);
    if (prev == null) {
        try {
            getSink().eventSunk(DefaultChannelPipeline.this, e);
        } catch (Throwable t) {
            notifyHandlerException(e, t);
        }
    } else {
        DefaultChannelPipeline.this.sendDownstream(prev, e);
    }
}

public void sendUpstream(ChannelEvent e) {
    DefaultChannelHandlerContext next = getActualUpstreamContext(this.next);
    if (next != null) {
        DefaultChannelPipeline.this.sendUpstream(next, e);
    }
}

正是因为这个实现,如果在一个末尾的ChannelUpstreamHandler中先移除自己,在向末尾添加一个新的ChannelUpstreamHandler,它是无效的,因为它的next已经在调用前就固定设置为null了。


ChannelPipeline作为ChannelHandler的容器,它还提供了各种增、删、改ChannelHandler链表中的方法,而且如果某个ChannelHandler还实现了LifeCycleAwareChannelHandler,则该ChannelHandler在被添加进ChannelPipeline或从中删除时都会得到同志:

public interface LifeCycleAwareChannelHandler extends ChannelHandler {
    void beforeAdd(ChannelHandlerContext ctx) throws Exception;
    void afterAdd(ChannelHandlerContext ctx) throws Exception;
    void beforeRemove(ChannelHandlerContext ctx) throws Exception;
    void afterRemove(ChannelHandlerContext ctx) throws Exception;
}

public interface ChannelPipeline {
    void addFirst(String name, ChannelHandler handler);
    void addLast(String name, ChannelHandler handler);
    void addBefore(String baseName, String name, ChannelHandler handler);
    void addAfter(String baseName, String name, ChannelHandler handler);
    void remove(ChannelHandler handler);
    ChannelHandler remove(String name);
    <T extends ChannelHandler> T remove(Class<T> handlerType);
    ChannelHandler removeFirst();
    ChannelHandler removeLast();
    void replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler);
    ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
    <T extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName, ChannelHandler newHandler);
    ChannelHandler getFirst();
    ChannelHandler getLast();
    ChannelHandler get(String name);
    <T extends ChannelHandler> T get(Class<T> handlerType);
    ChannelHandlerContext getContext(ChannelHandler handler);
    ChannelHandlerContext getContext(String name);
    ChannelHandlerContext getContext(Class<? extends ChannelHandler> handlerType);
    void sendUpstream(ChannelEvent e);
    void sendDownstream(ChannelEvent e);
    ChannelFuture execute(Runnable task);
    Channel getChannel();
    ChannelSink getSink();
    void attach(Channel channel, ChannelSink sink);
    boolean isAttached();
    List<String> getNames();
    Map<String, ChannelHandler> toMap();
}

在DefaultChannelPipeline的ChannelHandler链条的处理流程为:


http://www.blogjava.net/DLevin/archive/2015/09/04/427031.html


参考:

《Netty主页》
《Netty源码解读(四)Netty与Reactor模式》
《Netty代码分析》
Scalable IO In Java
Intercepting Filter Pattern

posted @ 2015-09-08 11:11 paulwong 阅读(562) | 评论 (0)编辑 收藏

最新版BOOTSTRAP 4.0教学


http://wiki.jikexueyuan.com/project/bootstrap4/

posted @ 2015-09-02 10:56 paulwong 阅读(525) | 评论 (0)编辑 收藏

张左峰的歪理邪说 之 Redmine 1.4.X 史上最全插件方案

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://salivaxiu.blog.51cto.com/330680/1613510

前面有一个0.9.X的插件推荐列表,但是太老了,更新一下!

 

PS.很多插件是我自己汉化,甚至付费的,并且进行小幅修改,所以仅供参考。。。。。

 

PPS.话说,搞Redmine这帮人,真是一群疯子,更新太快了。。。。。就不敢更新慢点么。。。。。。

 

Advanced roadmap & milestones plugin  0.7.0  高级路线图,里程碑,不解释
Author box  0.0.3  这个可以在Wiki侧边栏显示一个你的头像和信息,搞个人崇拜用的
CRM plugin 2.3.3-pro CRM插件,很好用,也很专业,付费的
Due Date Reminder plugin  0.2.1 超期提醒插件
Issue Hot Buttons Plugin  0.4.5 超级有用的插件,快速自定义功能按钮,很方便更新问题,尤其是一两项的时候
Redmine Add Subversion Links  0.0.5 为版本库增加直接的SVN链接
Redmine Assets plugin  0.0.1 软资产管理,各位亲们,如果你有几万个问题,里卖包含图片文档啥的,你要找一个特痛苦吧?这个插件帮你解决
Redmine Banner plugin 0.0.6 公告。。。超级实用。。。可以针对全局,也可以针对项目,发出公告
Redmine Better Gantt Chart plugin 0.6.5 更好的甘特图。。的确更好
Redmine CKEditor plugin 0.0.6 超级棒的Textile插件,替换Redmine那个简单的文本编辑器
Redmine Code Review plugin 0.4.8 代码评审,不解释
Redmine Default Version plugin 0.0.2 默认版本,给新建问题设置一个默认版本
Digest plugin 0.2.0 一个后台发送项目活动信息的插件,做日报,或者什么汇报用的DMSF 1.2.3 网页版的SVN,文档管家,支持HTML5,超级好用,但是界面稍显臃肿
Redmine Doodles plugin 0.5.1 投票,不解释
Redmine Glossary Plugin 0.7.0 名词解释插件,知识管理一部分
Redmine Good Job plugin 0.1.1 Goodjob....耍帅用的
Issue Importer 1.0 导入插件,超级实用,如果你有多个工作系统,切换来切换去,离不开这玩意,但是不建议新手使用,批量出错,特痛苦。
Redmine Information Plugin 0.2.5 这个也挺有用的,把系统的一些信息开放出来,比如宏,让大家参考
Redmine Attach Screenshot plugin 0.4.2 附件截图,好似用了Java
Redmine Issue Checklist plugin 1.0.3 Checklist,这个还解释么
Redmine Issue Extensions plugin 0.1.0 问题扩展插件,官方都用的,必须用啊
Redmine Issue Templates plugin 0.0.2.1 这是一个比较好用的插件,问题模板,尤其是一些重复性高,但是需要对过程管理的,配合
ChecklistKnowledgebase 1.0.0 知识库。。。我觉得也很实用的,不过有个BUG,需要你在数据库指定关联一下项目
List duplicate views plugin 0.0.5 好似可以检测到插件冲突。。。我就遇到过
Redmine Local Avatars plugin 0.1.1 本地头像,哈哈,我们断网的
Redmine Logs plugin 0.0.3 可以再后台直接看日志,不用登陆到服务器了
Redmine (Monitoring & Controlling | Monitoramento & Controle) 0.1.1 图形化的数据分析插件,装B用的(当然也有实际意义,宏观数据)
My Roadmaps plugin 0.1.12 我的路线图。。。。其实没啥用
Redmine News Balloon plugin 0.0.1 如果有新闻,会弹出一个气泡,提醒你
Niko-niko Calendar plugin 1.1.2 我最最最最喜欢的插件,感觉跟每天微博一样,挺有意思的
Redmine plugin views revisions plugin 0.0.1 忘了。。。。。
Preview attached files and attributes column plugin 0.1.7 附件的那个缩略图
Private Wiki 0.1.1 私有维基,Redmine最大的问题,在于权限管理不够细分,多这么一个插件,就相当增加一项特殊权限
Reorder links arbitrary 0.0.7 这个对管理员设置有帮助,可以快速设置排序
Smart issues sort plugin 0.3.1 智能排序,对经常用父任务的人,帮助很大
My Page Blocks plugin 1.2 (20120610) 自定义我的工作台,很好使!
Redmine Wiki Extensions plugin 0.4.1 维基扩展,不解释,官方都用
Wiki sidebar toc plugin 0.0.1 维基侧边栏吧?想不起来了
Redmine Wiki table of contents plugin 0.0.3 维基快速位置排序
Issues XLS export 0.2.1  问题列表XLS导出

 

我还没用的,但是考虑装上试试的列表,都是1.4.X可以用的

 

用户属性 http://www.redmine.org/plugins/userprofile
会议室预定 http://www.redmine.org/plugins/mmqb
费用、分票 http://www.redmine.org/plugins/invoices       需付费,可以考虑
免费发票 http://www.redmine.org/plugins/haltr
我的技能 http://www.redmine.org/plugins/redmine_coderwall
甘特图日期 http://www.redmine.org/plugins/redmine_gantt_with_date
登陆后跳转 http://www.redmine.org/plugins/landing_page
Wiki加密 http://www.redmine.org/plugins/redmine_wikicipher     可以考虑
Email过滤 http://www.redmine.org/plugins/redmine_email_notification_content_filter  可以考虑
另一个截图 http://www.redmine.org/plugins/javasript_screenshot
项目侧边栏 http://www.redmine.org/plugins/sidebar       可以考虑
匿名观察 http://www.redmine.org/plugins/anonymous-watchers     可以考虑
你的意思 http://www.redmine.org/plugins/didyoumean      可以考虑
隐身模式 http://www.redmine.org/plugins/redmine_stealth      可以考虑
发行说明 http://www.redmine.org/plugins/redmine_release_notes

论坛主题 http://www.redmine.org/plugins/redmine_boards
自定义邮件 http://www.redmine.org/plugins/notify_custom_users
日历假期 http://www.redmine.org/plugins/redmine_multi_calendar
多种上传 http://www.redmine.org/plugins/redmine_multiple_files_upload

Wiki
维基按钮 http://www.redmine.org/plugins/redmine_wiki_files_toolbar

Time
时间联系 http://www.redmine.org/plugins/linked_time_entries

版本库
SCM扩展  http://www.redmine.org/plugins/redmine_scm_extensions
编辑维基 http://www.redmine.org/plugins/redmine_require_wiki_comment

posted @ 2015-08-19 10:26 paulwong 阅读(2245) | 评论 (0)编辑 收藏

20 个有用的 Angular.js 工具

喜欢 Angular.js?我们为开发者编写了一份最佳 angular.js 工具和资源清单,这可让使用 angular 开发应用程序变得高效。

对于大多数想要设计动态 web 应用的开发者而言,Angular.js 成为了一个可以选择的框架。angularjs 开发者如果想开始一个 AngularJS 工程,为了采取成熟的方式开发网页,他们或许需要很多工具。

在开始使用 angular.js 之前,Angular.js 新手或许也想读一些优秀的 angular.js 书籍。

我们同样也编写了一份庞大的在线 angular.js 教程清单。

为了减轻使用 AngularJS 开发 web 应用的负担,这里列出了几个出色的工具,包括测试、前端开发、编辑、函数库、扩展、模块、代码生成器、网格工具。

shirleywong
shirleywong
翻译于 1周前

0人顶

 翻译的不错哦!

Angular.js 开发最佳 IDE

Angular.js 需要的大部分集成开发环境(IDE)和轻量级的编辑器列在了下面。

Webstorm

Webstorm是一个出色的代码编辑器,它完全理解工程,可以为任何类型的网站提供高质量的代码。它支持所有最流行最新的网站开发技术。使用这个工具开发网站,可以很好地集成单点或流程。 

Aptana

Aptana 是一具集成开发环境,可以帮助你优雅地创建网站应用。使用它做为 AngularJS 的集成开发工具,你需要在 Eclipse 商店激活 AngularJS Eclipse 扩展。

wancheng
wancheng
翻译于 1周前

0人顶

 翻译的不错哦!

文本编辑器 Sublime

对 AngularJS Web 开发者来说,方便的文本编辑工具是 Sublime。它帮助程序员使用快捷方式或几个按键实现编码。它具有很强的适应性,可以定制任何类型的编程环境。它也可以按照你的意愿分块编辑。它还可以轻松地在项目之间切换,所有的修改都将自动保存在各自的项目中。

Angular.js 的专用测试工具

测试是开发的重要组成部分,无论对于使用 Angular.js 还是任何其他软件都是如此。下面的工具可以帮助你简化采用 Angular.js 开发的应用程序的测试。

这些工具都是在线 JavaScript 测试工具的好伴侣。

Protractor

Protractor 是一个端到端的测试框架,贯穿于 AngularJS,是一个完全自动化的测试工具。它可以运行在真实的浏览器中测试你的应用程序。它使用了 WebDriver,Mocha, jasmine, Node.js, selenium 和cucumber 等都使用的伟大的技术。

当所有的待处理网页任务完成时,它会自动使用AngularJS的应用进行通信。所以,你在测试时不需要使用等待或睡眠命令。

Iam魔方
Iam魔方
翻译于 1周前

0人顶

 翻译的不错哦!

Jasmine

Jasmine 是一个行为驱动开发框架,专为 Javascript 用户创建。它提供了基本的测试框架,并且可以持续维护。所有测试都可以使用一个 all-in-one 的包完成,这是 Jasmine 的主要特点,高效测试你的应用。

这个框架的一个缺点是它不能感知运行平台(浏览器)。如果配合Karma使用这个问题很容易避免。

Karma 可以做为测试运行者配合 Jasmine 使用。它是一个测试框架帮且你高效地测试应用。 

Code Orchestra

它是一个绝对意义上的前端开发工具帮助你创建和测试网站应用。你可以实时写代码,根据建议修改代码,以同样的格式保存代码。通过这个工具修改后的代码会自动布署到运行中的应用。

wancheng
wancheng
翻译于 1周前

0人顶

 翻译的不错哦!

最好的 Angular.js 函数库

下面是一些有用的库,它们可以增强 angular.js 框架的能力,对开发者有所帮助。没有必要从头构建那些可能已经成为开放源码的函数库。

CodePen

对于所有 HTML,CSS 和 JavaScript 的前端开发者,CodePen 是一款完美的编辑工具。这个工具可以最大限度地减少对网站的创建、测试和完善的繁琐的网页开发工作。它是一个协作的在线编程环境。

Web 开发人员可以清晰地跨平台实时查看。它有一个浏览器中的代码编辑器,可以自动地迅速地上传多个文件。这个功能可协助 Web 开发人员在几秒的时间内创建一个新的代码。

Angular Fire

使用 Angular Fire,可以轻松地帮助你开发 AngularJS 的应用后台。AngularJS 绑定的 Firebase 已经正式被 Angular Fire 支持。Firebase 是一个基于云计算的平台,可以很容易地集成实时应用和快速创建后台。

当 Firebase 和 Angular Fire 组合在一起,它们有助于以更快的速度同步数据和提供良好的用户管理服务。它还提供了一个三向的数据绑定、用户身份验证和静态托管。

Iam魔方
Iam魔方
翻译于 7天前

0人顶

 翻译的不错哦!

AngularUI

AngularJS 以高效率创建单页面应用而出名. 创建这些单页面应用时候,我们需要一个灵活的路由,这个优秀的AngularJS框架是构建一个全面的UI组件俗称ui-router。 它能根据应用程序的状态提供一个简单的导航和改变视图,而不仅仅是基于URL。
AngularUI还包含非常多的UI组件,这些组件是使用原生指令像ui—maps,ui-calendar,ui-Bootstrap创建的。这些UI组件和指令可以更快建设Angular网站

UI Bootstrap

UI Bootstrap是一个不同寻常的AngularUI组件,它能帮助你创建基于智能手机的web应用程序,而且用户体验不错。这个UI组件提供的AngularJS原生指令完全兼容Twitte Bootstrap

成熟的毛毛虫
成熟的毛毛虫
翻译于 5天前

1人顶

 翻译的不错哦!

Angular.js 有用的扩展和工具

下面是一些 Angular.js 扩展,可以满足一些特殊应用之需。

Ng-Inspector

Ng-Inspector 是一个优秀的浏览器插件,支持 Firefox,Chrome 和 Safari,复用它可以创建一个探测控制面板,方便开发,调试 AngularJS 应用,它提供了完整的辅助功能。

使用它可以更方便的和你的应用交互,还可以实时更新。它还可以看到全部范围内的层次结构,模型,类型和值。点击你关注的一个范围,它会高亮显示相应的 DOM 结点。

AngularJS Batarang

你可以使用 AngularJS Batarang 来调试你的 AngularJ S应用,它是一个专为 Chrome 提供的插件。它帮助你改善应用性能。还可能衡量调节性能的进度。

wancheng
wancheng
翻译于 4天前

0人顶

 翻译的不错哦!

Restangular

AngularJS 独有的一个服务是 Restangular,它可以帮助您轻松应对各种要求,例如获取、发送、删除以及把数据存入数据库。它对于所有从 RESTful API 中大规模存取数据的 AngularJS 应用都很有必要。

Generator Angular - 一个有用的工具

Yeomen Generator

你可以很容易地开始一个具有合理的默认值和最好的用例的项目。建立这样的 Angular 应用,这款 Yeomen generator 工具是非常有用的。它只需几条终端的命令,便加速了 AngularJS 应用的开发过程。这个工具是非常有用的。这些专用的生成工具将有助于应用了解项目的有关信息,并有助于开发和测试应用程序。

Iam魔方
Iam魔方
翻译于 3天前

0人顶

 翻译的不错哦!

Angular Deckgrid

Angular Deckgrid 可以为你提供响应度和颜值俱高的应用,可以适配不同的移动终端。轻量级类砖石结构易于创建灵活的表格,高效创建图片展示。 

Radian

Radian 是一个优秀的框架,使用它只需要少量的设置就可以开启 AngularJS 项目。在多人开发项目中它是一个理想的选择。

Lumx

Lumx 以快速简单的方式帮助你创建简单而优雅的应用。 这个可响应式前端框架是基于AngularJS 和Google 材料设计规范。这个工具可嵌入最新的技术,如 Sass 预处理器,AngularJS 和 JQuery,能极大地提高 web 应用的性能。

wancheng
wancheng
翻译于 1天前

0人顶

 翻译的不错哦!

Angular Gettext

你可以用英语编码,在编码需要被翻译的地方加上注解。Angular Gettext 工具就会自动翻译那些独立的部分。这是 AngularJS 非常简单而强大的翻译支持工具。

NgDocs

AngularJS 框架内置 ngDocs 工具可以简化你项目文档和参考手册的相关工作。这款基于 Android 的工具也能提供给所有新手一些容易跟进的教程。

叶秀兰
叶秀兰
翻译于 18小时前

0人顶

 翻译的不错哦!

NgTables

无论是简单还是复杂的 Web 应用,在 AngularJS 框架中很容易创建一个表格,然后通过实用的 ngTables 工具进行高效的管理。ngTable 是 AngularJS 表格指令,支持排序,过滤和分页,在编译步骤中自动生成带有标题和过滤器的标题行。

ngTable 支持定制过滤选项,表格分组,表格外部数据控制 等等功能。

总的来说,这些都是创建任意 AngularJS Web 应用的,最有用的工具集合。用好这些工具可以帮助你轻松高效的创建 AngularJS 项目。

posted @ 2015-08-12 13:32 paulwong 阅读(623) | 评论 (0)编辑 收藏

排版六原则

作者: 阮一峰

日期: 2010年10月16日

上个月,我贴了《图形化简历》

几天后,就收到了秋叶老师的来信,希望与我探讨一些设计问题。他写过一本畅销书《说服力-让你的PPT会说话》,眼下正在写续集。

我看了新书的样章,觉得很不错,有些内容很值得分享。

====================================

首先,我们先看一个例子。良好的设计如何使得一个平庸的文档脱胎换骨。下面是一张大学生的求职简历,再普通不过了,想要引起招聘经理的注意,恐怕很难。

秋叶老师对它进行了简单的排版,还是一张表格,还是黑白配色,没有使用任何图形元素,效果却完全不一样了。

真是令人眼前一亮,不由自主地想多看几眼。这就是优秀设计的作用:它让你脱颖而出。

====================================

秋叶老师把他的排版心得,总结为六个原则:对齐,聚拢,重复,对比,强调,留白。我是这样理解的:

一、对齐原则

  相关内容必须对齐,次级标题必须缩进,方便读者视线快速移动,一眼看到最重要的信息。

二、聚拢原则

  将内容分成几个区域,相关内容都聚在一个区域中。段间距应该大于段内的行距。

三、留白原则

  千万不要把页面排得密密麻麻,要留出一定的空白,这本身就是对页面的分隔。这样既减少了页面的压迫感,又可以引导读者视线,突出重点内容。

四、降噪原则

  颜色过多、字数过多、图形过繁,都是分散读者注意力的"噪音"。

五、重复原则

  多页面排版时,注意各个页面设计上的一致性和连贯性。另外,在内容上,重要信息值得重复出现。

六、对比原则

  加大不同元素的视觉差异。这样既增加了页面的活泼,又方便读者集中注意力阅读某一个子区域。

====================================

下面用一个PPT的例子,演示排版六原则。

上面这张ppt有两个毛病。一是字数太多,抓不住重点;二是右边没有对齐,使得读者的视线只能一行行地从行首到行尾移动,不能直上直下。

现在进行修改。

第一步,根据"聚拢原则",将六点分成六个区域。

第二步,根据"降噪原则",将每一点分成"小标题"和"说明文字"两部分。

第三步,根据"对齐原则",将每一个部分、每一种元素对齐。

第四步,根据"对比原则",加大"小标题"和"说明文字"在字体和颜色上的差异。

第五步,根据"留白原则",留出一定的空白。

页面的可读性大大增加。

(完)

posted @ 2015-08-07 15:49 paulwong 阅读(724) | 评论 (0)编辑 收藏

关于颜色理论

作者: 阮一峰

日期: 2008年7月21日

制作网页的过程中,我一直不知道应该如何配色。

我的意思是,我不知道应该选择哪些颜色放在一起,完全凭感觉。于是昨天,我在网上找了一些资料,希望找到理论指导。

结果很失望。颜色理论研究的都是颜色的本质,至于颜色搭配,最终靠的还是个人感觉。说到底,Choosing colors is art, not science。不过,我还是记录一下吧,其中一些东西还是很有趣的。

=================

1. Color Wheel

所谓Color Wheel,就是将一系列颜色,有次序地通过一个圆盘的形式,展现出来。

它的产生方式是,首先列出三原色(PRIMARY COLORS):红、黄、蓝。

Photobucket

然后,二二混合,产生二级颜色(SECONDARY COLORS):绿、橙、紫。

接着,继续二二混合,又产生6种三级颜色(TERTIARY COLORS):黄橙、红橙、红紫、蓝紫、黄绿、蓝紫。

通过不断混合相邻颜色,产生新的颜色,最终形成一个全域的Color Wheel。

2. 类似色和互补色

12色的Color Wheel上任意三个相邻的颜色,被称为类似色(analogous colors)。通常认为,它们放在一起会很和谐。

Color Wheel对角线上的两种颜色,被称为互补色(complementary colors)。通常认为,它们放在一起,会形成对比效果。

此外,如果要寻找三种互相平衡的颜色,可以选择12色的Color Wheel上任意三个三角对立的颜色(Triad)。

如果要寻找三种颜色,其中二种互相类似,另一种与它们形成对比,则可以选取互补色两侧相邻的颜色。(split-complementary colors

3. 颜色模型

常用的颜色模型有三种,分别是RGB、CMYK、HSV模型。

4. RGB模型

RGB是Red、Green和Blue的缩写,任意颜色都可以由红、绿、蓝这三种颜色不同比例混合后产生。这个模型主要用于电子显示屏的颜色显示。

RGB模型通常用三个十六进制数来表示颜色,FFFFFF代表100%的红色、100%的绿色和100%蓝色混合,产生白色;000000代表0%的红色、0%的绿色和0%蓝色混合,产生黑色。

5. HSV模型

H指的是Hue(色调),它是"颜色"的同义词。

S指的是Saturation(饱和度),它指的是颜色的纯度,即颜色中含有灰色(gray)的程度。饱和度越高,颜色越纯;饱和度越低,颜色中灰色成分越大。任何颜色,饱和度变成最小值时,都会变成灰色。

V指的是Value,即颜色中白色的成分。这个值越大,颜色就越白越亮,这个值最小,颜色就越黑越暗。最大值时,所有颜色都变成白色,最小值时,所有颜色都变成黑色。

HSV模型是通过调节这三个值来标识颜色。它通常是一个color wheel的形式,所有边缘的颜色都是饱和度最高的颜色,越向圆心饱和度越小。Hue通过角度值选取,另有一个亮度轴,来选取Value值。

6.CMYK模型

这个模型主要用于印刷业,就是指用cyan, magenta, yellow, black这四种颜料混合,产生其他各种颜色。

印刷品上的图案,仔细看其实都是由一个个小点构成,而每个小点又都是采用四色套印,重合叠加后产生各种颜色的效果。

(完)

posted @ 2015-08-07 15:47 paulwong 阅读(647) | 评论 (0)编辑 收藏

RESTful API 设计指南

作者: 阮一峰

日期: 2014年5月22日

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备......)。

因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"API First"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。我以前写过一篇《理解RESTful架构》,探讨如何理解这个概念。

今天,我将介绍RESTful API的设计细节,探讨如何设计一套合理、好用的API。我的主要参考了两篇文章(12)。

RESTful API

一、协议

API与用户的通信协议,总是使用HTTPs协议

二、域名

应该尽量将API部署在专用域名之下。

 https://api.example.com 

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

 https://example.org/api/ 

三、版本(Versioning)

应该将API的版本号放入URL。

 https://api.example.com/v1/ 

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

四、路径(Endpoint)

路径又称"终点"(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
  • https://api.example.com/v1/employees

五、HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。

还有两个不常用的HTTP动词。

  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

六、过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
  • ?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

七、状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里

八、错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

 {     error: "Invalid API key" } 

九、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
  • POST /collection:返回新生成的资源对象
  • PUT /collection/resource:返回完整的资源对象
  • PATCH /collection/resource:返回完整的资源对象
  • DELETE /collection/resource:返回一个空文档

十、Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

 {"link": {   "rel":   "collection https://www.example.com/zoos",   "href":  "https://api.example.com/zoos",   "title": "List of zoos",   "type":  "application/vnd.yourformat+json" }} 

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。

Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

 {   "current_user_url": "https://api.github.com/user",   "authorizations_url": "https://api.github.com/authorizations",   // ... } 

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

 {   "message": "Requires authentication",   "documentation_url": "https://developer.github.com/v3" } 

上面代码表示,服务器给出了提示信息,以及文档的网址。

十一、其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

(完)

posted @ 2015-08-07 14:13 paulwong 阅读(776) | 评论 (0)编辑 收藏

理解OAuth 2.0

https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

https://aaronparecki.com/oauth-2-simplified

posted @ 2015-08-07 14:12 paulwong 阅读(520) | 评论 (0)编辑 收藏

XMLHttpRequest Level 2 使用指南

作者: 阮一峰

日期: 2012年9月 8日

XMLHttpRequest是一个浏览器接口,使得Javascript可以进行HTTP(S)通信。

最早,微软在IE 5引进了这个接口。因为它太有用,其他浏览器也模仿部署了,ajax操作因此得以诞生。

但是,这个接口一直没有标准化,每家浏览器的实现或多或少有点不同。HTML 5的概念形成后,W3C开始考虑标准化这个接口。2008年2月,就提出了XMLHttpRequest Level 2 草案。

这个XMLHttpRequest的新版本,提出了很多有用的新功能,将大大推动互联网革新。本文就对这个新版本进行详细介绍。

一、老版本的XMLHttpRequest对象

在介绍新版本之前,我们先回顾一下老版本的用法。

首先,新建一个XMLHttpRequest的实例。

  var xhr = new XMLHttpRequest();

然后,向远程主机发出一个HTTP请求。

  xhr.open('GET', 'example.php');

  xhr.send();

接着,就等待远程主机做出回应。这时需要监控XMLHttpRequest对象的状态变化,指定回调函数。

  xhr.onreadystatechange = function(){

    if ( xhr.readyState == 4 && xhr.status == 200 ) {

      alert( xhr.responseText );

    } else {

      alert( xhr.statusText );

    }

  };

上面的代码包含了老版本XMLHttpRequest对象的主要属性:

  * xhr.readyState:XMLHttpRequest对象的状态,等于4表示数据已经接收完毕。

  * xhr.status:服务器返回的状态码,等于200表示一切正常。

  * xhr.responseText:服务器返回的文本数据

  * xhr.responseXML:服务器返回的XML格式的数据

  * xhr.statusText:服务器返回的状态文本。

二、老版本的缺点

老版本的XMLHttpRequest对象有以下几个缺点:

  * 只支持文本数据的传送,无法用来读取和上传二进制文件。

  * 传送和接收数据时,没有进度信息,只能提示有没有完成。

  * 受到"同域限制"(Same Origin Policy),只能向同一域名的服务器请求数据。

三、新版本的功能

新版本的XMLHttpRequest对象,针对老版本的缺点,做出了大幅改进。

  * 可以设置HTTP请求的时限。

  * 可以使用FormData对象管理表单数据。

  * 可以上传文件。

  * 可以请求不同域名下的数据(跨域请求)。

  * 可以获取服务器端的二进制数据。

  * 可以获得数据传输的进度信息。

下面,我就一一介绍这些新功能。

四、HTTP请求的时限

有时,ajax操作很耗时,而且无法预知要花多少时间。如果网速很慢,用户可能要等很久。

新版本的XMLHttpRequest对象,增加了timeout属性,可以设置HTTP请求的时限。

  xhr.timeout = 3000;

上面的语句,将最长等待时间设为3000毫秒。过了这个时限,就自动停止HTTP请求。与之配套的还有一个timeout事件,用来指定回调函数。

  xhr.ontimeout = function(event){

    alert('请求超时!');

  }

目前,Opera、Firefox和IE 10支持该属性,IE 8和IE 9的这个属性属于XDomainRequest对象,而Chrome和Safari还不支持。

五、FormData对象

ajax操作往往用来传递表单数据。为了方便表单处理,HTML 5新增了一个FormData对象,可以模拟表单。

首先,新建一个FormData对象。

  var formData = new FormData();

然后,为它添加表单项。

  formData.append('username', '张三');

  formData.append('id', 123456);

最后,直接传送这个FormData对象。这与提交网页表单的效果,完全一样。

  xhr.send(formData);

FormData对象也可以用来获取网页表单的值。

  var form = document.getElementById('myform');

  var formData = new FormData(form);

  formData.append('secret', '123456'); // 添加一个表单项

  xhr.open('POST', form.action);

  xhr.send(formData);

六、上传文件

新版XMLHttpRequest对象,不仅可以发送文本信息,还可以上传文件。

假定files是一个"选择文件"的表单元素(input[type="file"]),我们将它装入FormData对象。

  var formData = new FormData();

  for (var i = 0; i < files.length;i++) {

    formData.append('files[]', files[i]);

  }

然后,发送这个FormData对象。

  xhr.send(formData);

七、跨域资源共享(CORS)

新版本的XMLHttpRequest对象,可以向不同域名的服务器发出HTTP请求。这叫做"跨域资源共享"(Cross-origin resource sharing,简称CORS)。

使用"跨域资源共享"的前提,是浏览器必须支持这个功能,而且服务器端必须同意这种"跨域"。如果能够满足上面的条件,则代码的写法与不跨域的请求完全一样。

  xhr.open('GET', 'http://other.server/and/path/to/script');

目前,除了IE 8和IE 9,主流浏览器都支持CORS,IE 10也将支持这个功能。服务器端的设置,请参考《Server-Side Access Control》

八、接收二进制数据(方法A:改写MIMEType)

老版本的XMLHttpRequest对象,只能从服务器取回文本数据(否则它的名字就不用XML起首了),新版则可以取回二进制数据。

这里又分成两种做法。较老的做法是改写数据的MIMEType,将服务器返回的二进制数据伪装成文本数据,并且告诉浏览器这是用户自定义的字符集。

  xhr.overrideMimeType("text/plain; charset=x-user-defined");

然后,用responseText属性接收服务器返回的二进制数据。

  var binStr = xhr.responseText;

由于这时,浏览器把它当做文本数据,所以还必须再一个个字节地还原成二进制数据。

  for (var i = 0, len = binStr.length; i < len; ++i) {

    var c = binStr.charCodeAt(i);

    var byte = c & 0xff;

  }

最后一行的位运算"c & 0xff",表示在每个字符的两个字节之中,只保留后一个字节,将前一个字节扔掉。原因是浏览器解读字符的时候,会把字符自动解读成Unicode的0xF700-0xF7ff区段。

八、接收二进制数据(方法B:responseType属性)

从服务器取回二进制数据,较新的方法是使用新增的responseType属性。如果服务器返回文本数据,这个属性的值是"TEXT",这是默认值。较新的浏览器还支持其他值,也就是说,可以接收其他格式的数据。

你可以把responseType设为blob,表示服务器传回的是二进制对象。

  var xhr = new XMLHttpRequest();

  xhr.open('GET', '/path/to/image.png');

  xhr.responseType = 'blob';

接收数据的时候,用浏览器自带的Blob对象即可。

  var blob = new Blob([xhr.response], {type: 'image/png'});

注意,是读取xhr.response,而不是xhr.responseText。

你还可以将responseType设为arraybuffer,把二进制数据装在一个数组里。

  var xhr = new XMLHttpRequest();

  xhr.open('GET', '/path/to/image.png');

  xhr.responseType = "arraybuffer";

接收数据的时候,需要遍历这个数组。

  var arrayBuffer = xhr.response;

  if (arrayBuffer) {

    var byteArray = new Uint8Array(arrayBuffer);

    for (var i = 0; i < byteArray.byteLength; i++) {

      // do something

    }
  }

更详细的讨论,请看Sending and Receiving Binary Data

九、进度信息

新版本的XMLHttpRequest对象,传送数据的时候,有一个progress事件,用来返回进度信息。

它分成上传和下载两种情况。下载的progress事件属于XMLHttpRequest对象,上传的progress事件属于XMLHttpRequest.upload对象。

我们先定义progress事件的回调函数。

  xhr.onprogress = updateProgress;

  xhr.upload.onprogress = updateProgress;

然后,在回调函数里面,使用这个事件的一些属性。

  function updateProgress(event) {

    if (event.lengthComputable) {

      var percentComplete = event.loaded / event.total;

    }

  }

上面的代码中,event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0。

与progress事件相关的,还有其他五个事件,可以分别指定回调函数:

  * load事件:传输成功完成。

  * abort事件:传输被用户取消。

  * error事件:传输中出现错误。

  * loadstart事件:传输开始。

  * loadEnd事件:传输结束,但是不知道成功还是失败。

十、阅读材料

  1. Introduction to XMLHttpRequest Level 2: 新功能的综合介绍。

  2. New Tricks in XMLHttpRequest 2:一些用法的介绍。

  3. Using XMLHttpRequest:一些高级用法,主要针对Firefox浏览器。

  4. HTTP Access Control:CORS综述。

  5. DOM access control using cross-origin resource sharing:CORS的9种HTTP头信息

  6. Server-Side Access Control:服务器端CORS设置。

  7. Enable CORS:服务端CORS设置。

(完)

posted @ 2015-08-07 12:24 paulwong 阅读(501) | 评论 (0)编辑 收藏

理解DOMString、Document、FormData、Blob、File、ArrayBuffer数据类型

     摘要: 一、XMLHttpRequest 2.0的家臣们我大学那会儿,一个称为Ajax的东西对前端行业造成了深远影响,不仅是JS语言,而包括前端地位、职位兴起以及工作分工等。抛开IE6浏览器不谈,其他浏览器的Ajax实际上都是借助XMLHttpRequest实现的。然后,好多年过去了,XMLHttpRequest带着两位家臣,DOMString和Document数据类型攻城略地,几乎一统天下。然时代是发展...  阅读全文

posted @ 2015-08-07 12:08 paulwong 阅读(833) | 评论 (0)编辑 收藏

仅列出标题
共116页: First 上一页 35 36 37 38 39 40 41 42 43 下一页 Last