2013年11月20日星期三

Html5实践之EventSource - winhu

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Html5实践之EventSource - winhu  阅读原文»

最近尝试了一下服务器端的推送,之前的做法都是客户端轮询,定时向服务器发送请求。但这造成了我的一些困扰:

1:轮询是由客户端发起的,那么在服务端就不能判别我要推送的内容是否已经过期,因为我很难判断某个信息是否已经推送给全部的客户端,那么服务端就需要缓存大量的数据。如果数据保存在数据库,那么还要每次请求都需要查询数据库,这对数据库和系统设计都是一个很大的挑战。

2:请求的频率太高,每次的请求包中含有同样的数据,这对pc来说也许算不得什么,但是对于移动客户端来讲,这应该不是最佳的方案。尤其是遇到还要做权限判断的时候,那么服务端的逻辑和效率也会造成用户体验的降低。

好在Html5为我们提供了一种方式:Server-Sent Events包含新的HTML元素EventSource和新的MIME类型 text/event-stream来完成我的需要。

因为是第一次接触Html5,w3school中也有对EventSource的说明和使用。于是马上开始着手实践。

页面脚本就不用说了,按照w3school的方式即可。

var source=new EventSource("demo_sse.php");
source.onmessage
=function(event)
{
document.getElementById(
"result").innerHTML+=event.data + "<br />";
};

服务端的代码也是如初一折,w3school提供了php和asp的代码:

//php方式
<?php
header(
'Content-Type: text/event-stream');
header(
'Cache-Control: no-cache');
$time
= date('r');
echo
"data: The server time is: {$time}\n\n";
flush();
?>

//asp方式
<%
Response.ContentType
="text/event-stream"
Response.Expires
=-1
Response.Write(
"data: " & now())
Response.Flush()
%>

//代码解释:

  • 把报头 "Content-Type" 设置为 "text/event-stream"
  • 规定不对页面进行缓存
  • 输出发送日期(始终以 "data: " 开头)
  • 向网页刷新输出数据

也许大家应该注意到,php和asp的案例有一点不一样,就是php推送的信息一个使用了"\n\n"作为结束标志,而asp却没有。而本人实践则是用asp.net+mvc,经过测试,如果不以"\n\n"作为结束标志,那么客户端将不能接收到推送的值。还有需要特别声明一下:推送的信息格式必须为”data:内容\n\n“,否则。。。

public void Subscribe()
{
HttpContext.Response.ContentType
= "text/event-stream";
HttpContext.Response.CacheControl
= "no-cache";
HttpContext.Response.Write(
"data:" + DateTime.Now.ToString()+ "\n\n");
HttpContext.Response.Flush();
}

至此,客户端应该可以收到服务端推送的值。而如此简单的结构真的可以完成我们需要的功能设计吗?
此例我们只是推送了一个当前时间,而我们实际要推送的值是不断变化的,不然也就没有推送的必要了。

于是我想到了将订阅的请求保存起来,当需要推送的时候,在对每个请求进行循环推送,于是有了下面的代码:

public class PublishService
{
private static IDictionary<string, HttpResponseBase> contexts = new Dictionary<string, HttpResponseBase>();
public static void AddHttpContext(HttpContextBase context)
{
var token = context.GetToken(”CookieName“);
if (!contexts.Keys.Contains(token))
contexts.Add(token, context.Response);
}

private static void Publish()
{
foreach (var context in contexts.Values)
{
context.ContentType
= "text/event-stream";
context.CacheControl
= "no-cache";
msg
= GetData(context.GetToken("CookieName"));
context.Write(
"data:" + msg + "\n\n");
context.Flush();
}
}
public void Subscribe()
{
PublishService.AddHttpContext(HttpContext);
PublishService.Publish();
}
}

可是在进行测试的时候Chrome告诉我:EventSource's response has a MIME type ("text/plain") that is not "text/event-stream". Aborting the connection.

而FF告诉我:Firefox 无法建立到 http://localhost:8000/Location/Notification/Subscribe 服务器的连接。

经过调试发现,在每次flush的时候发生异常:Server cannot flush a completed response.这究竟是为啥呢?不论是google,还是baidu,我都没能找到合适的答案,所以此案至今未结,如哪位知道请细说一二。

于是乎,我放弃了这种方式,转而就推送一个时间看看是什么效果。结果发现Chrome每隔3秒向客户端推送一次,而FF是每5秒推送一次。有了这样一个发现,那么服务端的设计就应该是另一个样子:

public <
Google C++测试框架系列高级篇:第一章 更多关于断言的知识 - 移山测试工作室黑灯老师  阅读原文»

原始链接:More Assertions

词汇表

现在你应该已经读完了入门篇并且会使用GTest来写测试。是时候来学一些新把戏了。这篇文档将教会你更多知识:用断言构造复杂的失败信息,传递致命失败,重用和加速你的test fixtures,以及在你的测试中使用不同的标志位。

版本号:v_0.1

更多关于断言的知识

这一章节将覆盖一些较少被使用,但非常重要的关于断言的知识。

1. 明确的声明成功或失败

以下我们提到的几个断言实际上并不对值或表达式进行测试。取而代之的是它们直接产生一个通过或失败。像大多数做做测试的宏一样,我们可以定制失败信息并且流定向到它们。

SUCCESS()直接产生一个通过。但这并不表明整个测试通过。仅当所有断言都通过这个测试才被认为是通过。

注:这个宏目前只存在于文档中,是一个空操作,不会产生任何用户可见的输出。但是以后我们会考虑添加有关信息。

FAIL();ADD_FAILURE();ADD_FAILURE_AT("file_path", line_number);

FAIL()产生一个致命失败,而ADD_FAILURE和ADD_FAILURE_AT生成非致命失败。在类似于switch-case的控制流程中决定测试是否通过,用这些宏比用bool表达式更直接方便。例如以下代码:

switch(expression) {
case 1: ... some checks ...
case 2: ... some other checks
...
default: FAIL() << "We shouldn't get here.";
}

在Linux,Windows和Mac上可用。

2. 异常断言

以下断言用来判断代码是否抛出异常。

致命断言非致命断言通过条件
ASSERT_THROW(statement, exception_type);EXPECT_THROW(statement, exception_type);抛出指定类型的异常
ASSERT_ANY_THROW(statement);EXPECT__ANY_THROW(statement);抛出任意类型的异常
ASSERT_NO_THROW(statement);EXPECT_NO_THROW(statement);没有抛出异常

例子:

ASSERT_THROW(Foo(5), bar_exception);

EXPECT_NO_THROW({
int n = 5;
Bar(
&n);
});

在Linux,Windows和Mac上可用。

3. 通过谓词断言提供更好的错误信息

尽管GTest提供了丰富的断言,但还是不能覆盖我们在现实中所能遇到的场景,而且也不可能预见所有的情况(这不是一个好主意)。有时用户想使用EXPECT_TRUE来测试一个复杂的表达式,但是却没有合适的宏。这使得你无法显示表达式某一部分的值,当出错之后你很难找到问题到底出在哪里。某些用户选择自己构造失败信息,然后流定向到EXPECT_TRUE()。但这么做问题非常多,特别是当这个表达式有副作用或者开销巨大。

GTest提供了三个选项来解决这个问题。

3.1 使用已有的bool类型函数

如果你的函数或函数体返回bool类型(或者可以被隐式的转换为bool类型),在谓词断言中使用这些函数的话,参数将自动被打印。

致命断言非致命断言通过条件
ASSERT_PRED1(pred1, val1);EXPECT_PRED1(pred1, val1);pred1(val1)返回true
ASSERT_PRED2(pred1, val1, val2);EXPECT_PRED2(pred1, val1, val2);pred2(val1, val2)返回true
.........

在上表中,predn是一个有n个参数的谓词函数或函数体,val1, val2, ...直到valn代表它的参数。断言当谓词判断在给定参数返回true的情况下通过,不然失败。当断言失败后,它就会打印每一个参数。不管通过还是失败,这些参数的值只会被计算一次。

请看以下例子:

// Returns true if m and n have no common divisors except 1.
bool MutuallyPrime(int m, int n) { ... }
const int a = 3;
const int b = 4;
const int c = 10;

断言EXPECT_PRED2(MutuallyPrime, a, b)会通过,而断言EXPECT_PRED2(MutuallyPrime, b, c)会失败并打印以下信息:

!MutuallyPrime(b, c) is false, where
b is 4
c is 10

注:

  1. 当使用ASSERT_PRED*或EXPECT_PRED*遇到编译错误"no matching function to call"时,请参考这篇文章如何解决。
  2. 当前我们支持的参数数量小于等于5。如果你希望支持更多的参数,请让我们知道。

在Linux,Windows和Mac上可用。

3.2 使用返回AssertionResult对象的函数

虽然EXPECT_PRED*和它的小伙伴们用起来很方便,但是语法却不能让人满意。对于不同数量的参数你必须使用不同的宏,这看上去更向Lisp而不是C++。现在::testing::AssertionResult来帮助你解决这个问题。

一个AssertionResult对象代表一次断言的结果(通过或失败,以及相关的信息)。你可以用以下任意一个工厂函数来创建一个AssertionResult对象。

namespace testing {

// Returns an AssertionResult object to indicate that an assertion has
// succeeded.
AssertionResult AssertionSuccess();

// Returns an AssertionResult object to indicate that an assertion has
// failed.
AssertionResult AssertionFailure();

}

你可以使用"<<"操作符把信息流定向到AssertionResult对象。

为了在bool断言(例如EXPECT_TRUE())中提供可读性更好的信息,你可以实现一个返回AssertionResult的函数而不是仅仅返回bool类型。例如,你可以这样定义IsEven()函数:

::testing::AssertionResult IsEven(int n) {
if ((n % 2) == 0

没有评论:

发表评论