Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

渲染模板时,user直接显示 user is not defined

$
0
0

渲染模板时,user直接显示 user is not defined,如果把user改成 locals.user 后虽然不会报错,但是locals.user一直是空值, 而我在下面index.js文件console出来确实有值。刚开始学node,希望大家帮个忙,谢谢

<div class="ui buttons">
    <div class="ui floating dropdown button">
      <i class="icon bars"></i>
      <div class="menu">
        <% if (user) { %>
          <a class="item" href="/posts?author=<%= user._id %>">个人主页</a>
          <div class="divider"></div>
          <a class="item" href="/posts/create">发表文章</a>
          <a class="item" href="/signout">登出</a>
        <% } else { %>
          <a class="item" href="/signin">登录</a>
          <a class="item" href="/signup">注册</a>
        <% } %>
      </div>
    </div>
  </div>
</div> 


ReferenceError: E:\study\demo\Node\myblog\views\posts.ejs:1
 >> 1| <%- include('header') %>

    2| 这是主页

    3| <%- include('footer') %>

E:\study\demo\Node\myblog\views\header.ejs:13
    11|   <body>

    12|   <%- include('components/nav') %>

 >> 13|   <%- include('components/nav-setting') %>

    14|   <%- include('components/notification') %>

E:\study\demo\Node\myblog\views\components\nav-setting.ejs:6
    4|       <i class="icon bars"></i>

    5|       <div class="menu">

 >> 6|         <% if (user) { %>

    7|           <a class="item" href="/posts?author=<%= user._id %>">个人主页</a>

    8|           <div class="divider"></div>

    9|           <a class="item" href="/posts/create">发表文章</a>


user is not defined
    at eval (eval at compile (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:549:12), <anonymous>:11:8)
    at returnedFn (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:580:17)
    at include (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:578:39)
    at eval (eval at compile (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:549:12), <anonymous>:17:17)
    at returnedFn (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:580:17)
    at include (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:578:39)
    at eval (eval at compile (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:549:12), <anonymous>:9:17)
    at returnedFn (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:580:17)
    at tryHandleCache (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:223:34)
    at View.exports.renderFile [as engine] (E:\study\demo\Node\myblog\node_modules\_ejs@2.5.7@ejs\lib\ejs.js:437:10)


\n```
主文件index.js

// 添加模板必需的三个变量
app.use(function (req, res, next) {
  res.locals.user = req.session.user;
  console.log(res.locals.user);
  res.locals.success = req.flash('success').toString();
  res.locals.error = req.flash('error').toString();
  next();
});

接口管理平台DOClever 5.1.0发布了,功能重磅更新,用户体验全面提升!

$
0
0

接口管理平台 DOClever 是一个商业化开源产品,完全免费。无论你是前端工程师,还是后端工程师,接口永远都是两者交互的桥梁,所以 DOClever 专为中小型团队量身打造,旨在解决接口的管理,测试与数据生成,实现真正的一体化解决方案。

目前DOClever已经发展到了5.0版本,拥有线上用户1w+,接口数10w+,每天日活200+,成功为滴滴,同程,58等互联网公司提供了快速接口服务,已经逐渐成长为一个成熟的接口解决方案,而新版本为了打造更好的用户体验,前端已经全部做了重构,我们接下来的发展方向是让DOClever立志成为一个项目中后阶段的掌舵手,从项目的接口,文档,测试三个方面为开发者们提供更强大,更快捷的服务!

新版本更新如下:

1.新增了接口运行参数实例,接口的定义和具体参数值进行了分离 2.新增了接口模板功能,可以把通用的接口数据保存为一个模板,方便复用,新增接口也可以从这个模板创建 3.新增了JSON节点复制粘贴功能,复杂的JSON接口可以复用啦 4.新增了项目word导出功能 5.新增了项目在线实时分享功能,现在没有注册DOClever的用户也可以实时浏览项目了。 6.新增了接口运行页的Request Header标签,可以观察接口的请求头部,完整追溯一个接口的运行全过程 7.优化了诸多界面显示,比如参数的已填值鼠标移动上去会浮动展示,JSON节点的添加会在你点击添加的下一个节点新增节点等。 8.修复了上一个版本的诸多bug

DOClever可以为您做哪些事情:

1.可以对接口信息进行编辑管理,支持get,post,put,delete,patch五种方法,支持http和https协议,并且支持query,body,json,raw,rest,formdata的参数可视化编辑。同时对json可以进行无限层次可视化编辑。并且,状态码,代码注入,markdown文档等附加功能应有尽有。

2.接口调试运行,一个都不能少,可以对参数进行加密,从md5到aes一应俱全,返回参数与模型实时分析对比,给出不一致的地方,找出接口可能出现的问题。如果你不想手写文档,那么试试接口的数据生成功能,可以对接口运行的数据一键生成文档信息。

3.mock的无缝整合,DOClever自己就是一个mock服务器,当你把接口的开发状态设置成已完成,本地mock便会自动请求真实接口数据,否则返回事先定义好的mock数据。

4.支持postman,rap,swagger的导入,方便你做无缝迁移,同时也支持html文件的导出,方便你离线浏览!

5.项目版本和接口快照功能并行,你可以为一个项目定义1.0,1.1,1.2版本,并且可以自由的在不同版本间切换回滚,再也不怕接口信息的遗失,同时接口也有快照功能,当你接口开发到一半或者接口需求变更的时候,可以随时查看之前编辑的接口信息。

6.自动化测试功能,目前市面上类似平台的接口自动化测试大部分都是伪自动化,对于一个复杂的场景,比如获取验证码,登陆,获取订单列表,获取某个特定订单详情这样一个上下文关联的一系列操作无能为力。而DOClever独创的自动化测试功能,只需要你编写极少量的javascript代码便可以在网页里完成这样一系列操作,同时,DOClever还提供了后台定时批量执行测试用例并把结果发送到团队成员邮箱的功能,你可以及时获取接口的运行状态。

7.团队协作功能,很多类似的平台这样的功能是收费的,但是DOClever觉得好东西需要共享出来,你可以新建一个团队,并且把团队内的成员都拉进来,给他们分组,给他们分配相关的项目以及权限,发布团队公告等等。

8.DOClever开源免费,支持内网部署,很多公司考虑到数据的安全性,不愿意把接口放到公网上,没有关系,DOClever给出一个方便快捷的解决方案,你可以把平台放到自己的内网上,完全不需要连接外网,同时功能一样也不少,即便是对于产品的升级,DOClever也提供了很便捷的升级方案!

下个版本发布计划:

支持websocket和http头安全验证

DOClever,让接口更懂你! 产品地址:http://doclever.cn GitHub:https://github.com/sx1989827/DOClever

重构:从Promise到Async/Await

$
0
0

摘要:夸张点说,技术的发展与历史一样,顺之者昌,逆之者亡。JS开发者们,赶紧拥抱Async/Await吧!

早在半年多之前,我就在鼓吹Async/Await替代Promise的6个理由,似乎还招致了一些批评。然而,直到最近,我才真正开始进行代码重构,抛弃Promise,全面使用Async/Await。因为,Node 8终于LTS了

Async/Await真的比Promise好吗?

是的是的。

这些天,我大概重构了1000行代码,最大的感觉是代码简洁了很多:

  • 真正地用同步的方式写异步代码
  • 不用写then及其回调函数,减少代码行数,也避免了代码嵌套
  • 所有异步调用可以写在同一个代码块中,无需定义多余的中间变量
  • async函数会隐式地返回一个Promise,因此可以直接return变量,无需使用Promise.resolve进行转换

下面,我们可以通过一个非常简单的示例来体验一下Async/Await的酸爽:

示例1

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)

// 使用Promise
function usePromise()
{
    let a
    readFile("a.txt", "utf8")
        .then(tmp =>
        {
            a = tmp
            return readFile("b.txt", "utf8")
        })
        .then(b =>
        {
            let result = a + b
            console.log(result) // 输出"Hello, Fundebug!"
        })

}

// 使用Async/Await
async function useAsyncAwait()
{
    let a = await readFile("a.txt", "utf8")
    let b = await readFile("b.txt", "utf8")
    let result = a + b
    console.log(result) // 输出"Hello, Fundebug!"
}

usePromise()
useAsyncAwait()

由示例可知,使用Async/Await极大地简化了代码,使得代码可读性提高了非常多。

Async/Await真的替代了Promise?

是的是的。

对于Async/Await替代Promise的6个理由,批评者执着于Async/Await是基于Promise实现的,因此替代这个词不准确,这就有点尴尬了。

一方面,这里替代的是异步代码的编写方式,并非完全抛弃大家心爱的Promise,地球人都知道Async/Await是基于Promise的,不用太伤心;另一方面,Promise是基于回调函数实现的,那Promise也没有替代回调函数咯?

重构代码之后,我仍然用到了Promise库bluebird。“Talk is cheap, Show me the code!”,大家不妨看看两个示例。

示例2:Promise.promisify

使用Promise.promisify将不支持Promise的方法Promise化,调用异步接口的时候有两种方式:

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)

// 使用Promise
function usePromise()
{
    readFile("b.txt", "utf8")
        .then(b =>
        {
            console.log(b)
        })
}

// 使用Async/Await
async function useAsyncAwait()
{
    var b = await readFile("b.txt", "utf8")
    console.log(b) // 输出"Fundebug!"
}

usePromise()
useAsyncAwait()

Fundebug是全栈JavaScript错误监控平台,支持各种前端和后端框架,可以帮助您第一时间发现BUG!

示例3:Promise.map

使用Promise.map读取多个文件的数据,调用异步接口的时候有两种方式:

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)
var files = ["a.txt", "b.txt"]

// 使用Promise
function usePromise()
{
    Promise.map(files, file =>
        {
            return readFile(file, "utf8")
        })
        .then(results =>
        {
            console.log(results)
        })
}

// 使用Async/Await
async function useAsyncAwait()
{
    var results = await Promise.map(files, file =>
    {
        return readFile(file, "utf8")
    })
    console.log(results)
}

usePromise()
useAsyncAwait()

没错,我的确使用了Promise库,readFile与Promise.map都是Promise函数。但是,在调用readFile与Promise.map函数时,使用Async/Await与使用Promise是两种不同写法,它们是相互替代的关系。

Async/Await有什么问题吗?

有啊有啊。

使用了await的函数定义时要加一个async,调用异步函数的时候需要加一个await,这玩意写多了也觉着烦,有时候还容易忘掉。不写async代码直接报错,不写await代码执行会出错。

示例4

const Promise = require("bluebird")
var readFile = Promise.promisify(require("fs").readFile)

// 没有Async
function withoutAsync()
{
    let b = await readFile("b.txt", "utf8") // 报错"SyntaxError: Unexpected identifier"
    console.log(b) 
}

// 没有await
async function withoutAwait()
{
    let b = readFile("b.txt", "utf8")
    console.log(b) // 打印"Promise..."
}

withoutAsync()
withoutAwait()

既然Async/Await写着有点添乱,可不可以不写呢?我想以后应该是可以的,只要能够自动识别异步代码就行了,这应该也是未来的发展方向。至于说如何实现,那我就不知道了哎。

总结

JavaScript的异步编写方式,从回调函数到Promise再到Async/Await,表面上只是写法的变化,本质上则是语言层的一次次抽象,让我们可以用更简单的方式实现同样的功能,而程序员不需要去考虑代码是如何执行的。在我看来,这样的进步应该不会停止,有一天我们也许不用写Async/Await了!

参考

qq3.JPG

第一次在 npmjs 上发布包 _(:з)∠)_

Node.js 中遇到含空格 URL 的神奇“Bug”——小范围深入 HTTP 协议

$
0
0

本文首发于知乎专栏蚂蚁金服体验科技

首先声明,我在“Bug”字眼上加了引号,自然是为了说明它并非一个真 Bug。

问题抛出

昨天有个童鞋在看后台监控的时候,突然发现了一个错误:

[error] 000001#0: ... upstream prematurely closed connection while reading response header from upstream.
  client: 10.10.10.10
  server: foo.com
  request: "GET /foo/bar?rmicmd,begin run clean docker images job HTTP/1.1"
  upstream: "http://..."

大概意思就是说:一台服务器通过 HTTP 协议去请求另一台服务器的时候,单方面被对方服务器断开了连接——并且并没有任何返回。

开始重现

客户端 CURL 指令

其实这次请求的一些猫腻很容易就能发现——在 URL 中有空格。所以我们能简化出一条最简单的 CURL 指令:

$ curl "http://foo/bar baz" -v

**注意:**不带任何转义。

最小 Node.js 源码

好的,那么接下去开始写相应的最简单的 Node.js HTTP 服务端源码。

'use strict';

const http = require('http');

const server = http.createServer(function(req, resp) {
    console.log('');
    resp.end('hello world');
});

server.listen(5555);

大功告成,启动这段 Node.js 代码,开始试试看上面的指令吧。

如果你也正在跟着尝试这件事情的话,你就会发现 Node.js 的命令行没有输出任何信息,尤其是嘲讽的 '',而在 CURL 的结果中,你将会看见:

$ curl 'http://127.0.0.1:5555/d d' -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5555 (#0)
> GET /d d HTTP/1.1
> Host: 127.0.0.1:5555
> User-Agent: curl/7.54.0
> Accept: */*
>
* Empty reply from server
* Connection #0 to host 127.0.0.1 left intact
curl: (52) Empty reply from server

瞧,Empty reply from server

Nginx

发现了问题之后,就有另一个问题值得思考了:就 Node.js 会出现这种情况呢,还是其它一些 HTTP 服务器也会有这种情况呢。

于是拿小白鼠 Nginx 做了个实验。我写了这么一个配置:

server {
    listen 5555;

    location / {
        return 200 $uri;
    }
}

接着也执行一遍 CURL,得到了如下的结果:

$ curl 'http://127.0.0.1:5555/d d' -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5555 (#0)
> GET /d d HTTP/1.1
> Host: 127.0.0.1:5555
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: openresty/1.11.2.1
< Date: Tue, 12 Dec 2017 09:07:56 GMT
< Content-Type: application/octet-stream
< Content-Length: 4
< Connection: keep-alive
<
* Connection #0 to host xcoder.in left intact
/d d

厉害了,我的 Nginx

于是乎,理所当然,我暂时将这个事件定性为 Node.js 的一个 Bug。

Node.js 源码排查

认定了它是个 Bug 之后,我就开始了一贯的看源码环节——由于这个 Bug 的复现条件比较明显,我暂时将其定性为“Node.js HTTP 服务端模块在接到请求后解析 HTTP 数据包的时候解析 URI 时出了问题”。

http.js -> _http_server.js -> _http_common.js

源码以 Node.js 8.9.2为准。

这里先预留一下我们能马上想到的 node_http_parser.cc,而先讲这几个文件,是有原因的——这涉及到最后的一个应对方式。

首先看看 lib/http.js的相应源码:

...
const server = require('_http_server');

const { Server } = server;

function createServer(requestListener) {
  return new Server(requestListener);
}

那么,马上进入 lib/_http_server.js看吧。

首先是创建一个 HttpParser 并绑上监听获取到 HTTP 数据包后解析结果的回调函数的代码:

const {
  parsers,
  ...
} = require('_http_common');

function connectionListener(socket) {
  ...

  var parser = parsers.alloc();
  parser.reinitialize(HTTPParser.REQUEST);
  parser.socket = socket;
  socket.parser = parser;
  parser.incoming = null;

  ...

  state.onData = socketOnData.bind(undefined, this, socket, parser, state);
  ...
  socket.on('data', state.onData);

  ...
}

function socketOnData(server, socket, parser, state, d) {
  assert(!socket._paused);
  debug('SERVER socketOnData %d', d.length);

  var ret = parser.execute(d);
  onParserExecuteCommon(server, socket, parser, state, ret, d);
}

从源码中文我们能看到,当一个 HTTP 请求过来的时候,监听函数 connectionListener()会拿着 Socket 对象加上一个 data事件监听——一旦有请求连接过来,就去执行 socketOnData()函数。

而在 socketOnData()函数中,做的主要事情就是 parser.execute(d)来解析 HTTP 数据包,在解析完成后执行一下回调函数 onParserExecuteCommon()

至于这个 parser,我们能看到它是从 lib/_http_common.js中来的。

var parsers = new FreeList('parsers', 1000, function() {
  var parser = new HTTPParser(HTTPParser.REQUEST);

  ...

  parser[kOnHeaders] = parserOnHeaders;
  parser[kOnHeadersComplete] = parserOnHeadersComplete;
  parser[kOnBody] = parserOnBody;
  parser[kOnMessageComplete] = parserOnMessageComplete;
  parser[kOnExecute] = null;

  return parser;
});

能看出来 parsersHTTPParser的一条 Free List(效果类似于最简易的动态内存池),每个 Parser 在初始化的时候绑定上了各种回调函数。具体的一些回调函数就不细讲了,有兴趣的童鞋可自行翻阅。

这么一来,链路就比较明晰了:

请求进来的时候,Server 对象会为该次请求的 Socket 分配一个 HttpParser对象,并调用其 execute()函数进行解析,在解析完成后调用 onParserExecuteCommon()函数。

node_http_parser.cc

我们在 lib/_http_common.js中能发现,HTTPParser的实现存在于 src/node_http_parser.cc中:

const binding = process.binding('http_parser');
const { methods, HTTPParser } = binding;

至于为什么 const binding = process.binding('http_parser')就是对应到 src/node_http_parser.cc文件,以及这一小节中下面的一些 C++ 源码相关分析,不明白且有兴趣的童鞋可自行去阅读更深一层的源码,或者网上搜索答案,或者我提前无耻硬广一下我快要上市的书《Node.js:来一打 C++ 扩展》——里面也有说明,以及我的有一场知乎 Live《深入理解 Node.js 包与模块机制》。

总而言之,我们接下去要看的就是 src/node_http_parser.cc了。

env->SetProtoMethod(t, "close", Parser::Close);
env->SetProtoMethod(t, "execute", Parser::Execute);
env->SetProtoMethod(t, "finish", Parser::Finish);
env->SetProtoMethod(t, "reinitialize", Parser::Reinitialize);
env->SetProtoMethod(t, "pause", Parser::Pause<true>);
env->SetProtoMethod(t, "resume", Parser::Pause<false>);
env->SetProtoMethod(t, "consume", Parser::Consume);
env->SetProtoMethod(t, "unconsume", Parser::Unconsume);
env->SetProtoMethod(t, "getCurrentBuffer", Parser::GetCurrentBuffer);

如代码片段所示,前文中 parser.execute()所对应的函数就是 Parser::Execute()了。

class Parser : public AsyncWrap {
  ...

  static void Execute(const FunctionCallbackInfo<Value>& args) {
    Parser* parser;
    ...

    Local<Object> buffer_obj = args[0].As<Object>();
    char* buffer_data = Buffer::Data(buffer_obj);
    size_t buffer_len = Buffer::Length(buffer_obj);

    ...

    Local<Value> ret = parser->Execute(buffer_data, buffer_len);

    if (!ret.IsEmpty())
      args.GetReturnValue().Set(ret);
  }

  Local<Value> Execute(char* data, size_t len) {
    EscapableHandleScope scope(env()->isolate());

    current_buffer_len_ = len;
    current_buffer_data_ = data;
    got_exception_ = false;

    size_t nparsed =
      http_parser_execute(&parser_, &settings, data, len);

    Save();

    // Unassign the 'buffer_' variable
    current_buffer_.Clear();
    current_buffer_len_ = 0;
    current_buffer_data_ = nullptr;

    // If there was an exception in one of the callbacks
    if (got_exception_)
      return scope.Escape(Local<Value>());

    Local<Integer> nparsed_obj = Integer::New(env()->isolate(), nparsed);
    // If there was a parse error in one of the callbacks
    // TODO(bnoordhuis) What if there is an error on EOF?
    if (!parser_.upgrade && nparsed != len) {
      enum http_errno err = HTTP_PARSER_ERRNO(&parser_);

      Local<Value> e = Exception::Error(env()->parse_error_string());
      Local<Object> obj = e->ToObject(env()->isolate());
      obj->Set(env()->bytes_parsed_string(), nparsed_obj);
      obj->Set(env()->code_string(),
               OneByteString(env()->isolate(), http_errno_name(err)));

      return scope.Escape(e);
    }
    return scope.Escape(nparsed_obj);
  }
}

首先进入 Parser的静态 Execute()函数,我们看到它把传进来的 Buffer转化为 C++ 下的 char*指针,并记录其数据长度,同时去执行当前调用的 parser对象所对应的 Execute()函数。

在这个 Execute()函数中,有个最重要的代码,就是:

size_t nparsed =
    http_parser_execute(&parser_, &settings, data, len);

这段代码是调用真正解析 HTTP 数据包的函数,它是 Node.js 这个项目的一个自研依赖,叫 http-parser。它独立的项目地址在 https://github.com/nodejs/http-parser,我们本文中用的是 Node.js v8.9.2 中所依赖的源码,应该会有偏差。

http-parser

HTTP Request 数据包体

如果你已经对 HTTP 包体了解了,可以略过这一节。

HTTP 的 Request 数据包其实是文本格式的,在 Raw 的状态下,大概是以这样的形式存在:

方法 URI HTTP/版本
头1: 我是头1
头2: 我是头2

简单起见,这里就写出最基础的一些内容,至于 Body 什么的大家自己找资料看吧。

上面的是什么意思呢?我们看看 CURL 的结果就知道了,实际上对应 curl ... -v的中间输出:

GET /test HTTP/1.1
Host: 127.0.0.1:5555
User-Agent: curl/7.54.0
Accept: */*

所以实际上大家平时在文章中、浏览器调试工具中看到的什么请求头啊什么的,都是以文本形式存在的,以换行符分割。

而——重点来了,导致我们本文所述“Bug”出现的请求,它的请求包如下:

GET /foo bar HTTP/1.1
Host: 127.0.0.1:5555
User-Agent: curl/7.54.0
Accept: */*

重点在第一行:

GET /foo bar HTTP/1.1

源码解析

话不多少,我们之间前往 http-parser 的 http_parser.chttp_parser_execute ()函数中的状态机变化。

从源码中文我们能看到,http-parser 的流程是从头到尾以 O(n) 的时间复杂度对字符串逐字扫描,并且不后退也不往前跳。

那么扫描到每个字符的时候,都有属于当前的一个状态,如“正在扫描处理 uri”、“正在扫描处理 HTTP 协议并且处理到了 H”、“正在扫描处理 HTTP 协议并且处理到了 HT”、“正在扫描处理 HTTP 协议并且处理到了 HTT”、“正在扫描处理 HTTP 协议并且处理到了 HTTP”、……

这是回音你懂吗

憋笑,这是真的,我们看看代码就知道了:

case s_req_server:
case s_req_server_with_at:
case s_req_path:
case s_req_query_string_start:
case s_req_query_string:
case s_req_fragment_start:
case s_req_fragment:
{
  switch (ch) {
    case ' ':
      UPDATE_STATE(s_req_http_start);
      CALLBACK_DATA(url);
      break;
    case CR:
    case LF:
      parser->http_major = 0;
      parser->http_minor = 9;
      UPDATE_STATE((ch == CR) ?
        s_req_line_almost_done :
        s_header_field_start);
      CALLBACK_DATA(url);
      break;
    default:
      UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
      if (UNLIKELY(CURRENT_STATE() == s_dead)) {
        SET_ERRNO(HPE_INVALID_URL);
        goto error;
      }
  }
  break;
}

在扫描的时候,如果当前状态是 URI 相关的(如 s_req_paths_req_query_string等),则执行一个子 switch,里面的处理如下:

  • 若当前字符是空格,则将状态改变为 s_req_http_start并认为 URI 已经解析好了,通过宏 CALLBACK_DATA()触发 URI 解析好的事件;
  • 若当前字符是换行符,则说明还在解析 URI 的时候就被换行了,后面就不可能跟着 HTTP 协议版本的申明了,所以设置默认的 HTTP 版本为 0.9,并修改当前状态,最后认为 URI 已经解析好了,通过宏 CALLBACK_DATA()触发 URI 解析好的事件;
  • 其余情况(所有其它字符)下,通过调用 parse_url_char()函数来解析一些东西并更新当前状态。(因为哪怕是在解析 URI 状态中,也还有各种不同的细分,如 s_req_paths_req_query_string

这里的重点还是当状态为解析 URI 的时候遇到了空格的处理,上面也解释过了,一旦遇到这种情况,则会认为 URI 已经解析好了,并且将状态修改为 s_req_http_start。也就是说,有“Bug”的那个数据包 GET /foo bar HTTP/1.1在解析到 foo后面的空格的时候它就将状态改为 s_req_http_start并且认为 URI 已经解析结束了。

好的,接下来我们看看 s_req_http_start怎么处理:

case s_req_http_start:
  switch (ch) {
    case 'H':
      UPDATE_STATE(s_req_http_H);
      break;
    case ' ':
      break;
    default:
      SET_ERRNO(HPE_INVALID_CONSTANT);
      goto error;
  }
  break;

case s_req_http_H:
  STRICT_CHECK(ch != 'T');
  UPDATE_STATE(s_req_http_HT);
  break;

case s_req_http_HT:
  ...

case s_req_http_HTT:
  ...

case s_req_http_HTTP:
  ...

case s_req_first_http_major:
  ...

如代码所见,若当前状态为 s_req_http_start,则先判断当前字符是不是合标。因为就 HTTP 请求包体的格式来看,如果 URI 解析结束的话,理应出现类似 HTTP/1.1的这么一个版本申明。所以这个时候 http-parser 会直接判断当前字符是否为 H

  • 若是 H,则将状态改为 s_req_http_H并继续扫描循环的下一位,同理在 s_req_http_H下若合法状态就会变成 s_req_http_HT,以此类推; +若是空格,则认为是多余的空格,那么当前状态不做任何改变,并继续下一个扫描;
  • 但如果当前字符既不是空格也不是 H,那么好了,http-parser 直接认为你的请求包不合法,将你本次的解析设置错误 HPE_INVALID_CONSTANTgotoerror代码块。

至此,我们基本上已经明白了原因了:

http-parser 认为在 HTTP 请求包体中,第一行的 URI 解析阶段一旦出现了空格,就会认为 URI 解析完成,继而解析 HTTP 协议版本。但若此时紧跟着的不是 HTTP 协议版本的标准格式,http-parser 就会认为你这是一个 HPE_INVALID_CONSTANT的数据包。

不过,我们还是继续看看它的 error代码块吧:

error:
  if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
    SET_ERRNO(HPE_UNKNOWN);
  }

  RETURN(p - data);

这段代码中首先判断一下当跳到这段代码的时候有没有设置错误,若没有设置错误则将错误设置为未知错误(HPE_UNKNOWN),然后返回已解析的数据包长度。

p是当前解析字符指针,data是这个数据包的起始指针,所以 p - data就是已解析的数据长度。如果成功解析完,这个数据包理论上是等于这个数据包的完整长度,若不等则理论上说明肯定是中途出错提前返回。

回到 node_http_parser.cc

看完了 http-parser 的原理后,很多地方茅塞顿开。现在我们回到它的调用地 node_http_parser.cc继续阅读吧。

Local<Value> Execute(char* data, size_t len) {
  ...

  size_t nparsed =
    http_parser_execute(&parser_, &settings, data, len);

  Local<Integer> nparsed_obj = Integer::New(env()->isolate(), nparsed);
  if (!parser_.upgrade && nparsed != len) {
    enum http_errno err = HTTP_PARSER_ERRNO(&parser_);

    Local<Value> e = Exception::Error(env()->parse_error_string());
    Local<Object> obj = e->ToObject(env()->isolate());
    obj->Set(env()->bytes_parsed_string(), nparsed_obj);
    obj->Set(env()->code_string(),
             OneByteString(env()->isolate(), http_errno_name(err)));

    return scope.Escape(e);
  }
  return scope.Escape(nparsed_obj);
}

从调用处我们能看见,在执行完 http_parser_execute()后有一个判断,若当前请求不是 upgrade请求(即请求头中有说明 Upgrade,通常用于 WebSocket),并且解析长度不等于原数据包长度(前文说了这种情况属于出错了)的话,那么进入中间的错误代码块。

在错误代码块中,先 HTTP_PARSER_ERRNO(&parser_)拿到错误码,然后通过 Exception::Error()生成错误对象,将错误信息塞进错误对象中,最后返回错误对象。

如果没错,则返回解析长度(nparsed_objnparsed)。

在这个文件中,眼尖的童鞋可能发现了,执行 Execute()有好多处,这是因为实际上一个 HTTP 请求可能是流式的,所以有时候可能会只拿到部分数据包。所以最后有一个结束符需要被确认。这也是为什么 http-parser 在解析的时候只能逐字解析而不能跳跃或者后退了。

回到 _http_server.js

我们把 Parser::Execute()也就是 JavaScript 代码中的 parser.execute()给搞清楚后,我们就能回到 _http_server.js看代码了。

前文说了,socketOnData在解析完数据包后会执行 onParserExecuteCommon函数,现在就来看看这个 onParserExecuteCommon()函数。

function onParserExecuteCommon(server, socket, parser, state, ret, d) {
  resetSocketTimeout(server, socket, state);

  if (ret instanceof Error) {
    debug('parse error', ret);
    socketOnError.call(socket, ret);
  } else if (parser.incoming && parser.incoming.upgrade) {
    ...
  }
}

长长的一个函数被我精简成这么几句话,重点很明显。ret就是从 socketOnData传进来已解析的数据长度,但是在 C++ 代码中我们也看到了它还有可能是一个错误对象。所以在这个函数中一开始就做了一个判断,判断解析的结果是不是一个错误对象,如果是错误对象则调用 socketOnError()

function socketOnError(e) {
  // Ignore further errors
  this.removeListener('error', socketOnError);
  this.on('error', () => {});

  if (!this.server.emit('clientError', e, this))
    this.destroy(e);
}

我们看到,如果真的不小心走到这一步的话,HTTP Server 对象会触发一个 clientError事件。

整个事情串联起来了:

  • 收到请求后会通过 http-parser 解析数据包;
  • GET /foo bar HTTP/1.1会被解析出错并返回一个错误对象;
  • 错误对象会进入 if (ret instanceof Error)条件分支并调用 socketOnError()函数;
  • socketOnError()函数中会对服务器触发一个 clientError事件;(this.server.emit('clientError', e, this)
  • 至此,HTTP Server 并不会走到你的那个 function(req, resp)中去,所以不会有任何的数据被返回就结束了,也就解答了一开始的问题——收不到任何数据就请求结束。

这就是我要逐级进来看代码,而不是直达 http-parser 的原因了——clientError是一个关键。

处理办法

要解决这个“Bug”其实不难,直接监听 clientError事件并做一些处理即可。

'use strict';

const http = require('http');

const server = http.createServer(function(req, resp) {
    console.log('');
    resp.end('hello world');
}).on('clientError', function(err, sock) {
    console.log('');
    sock.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(5555);

**注意:**由于运行到 clientError事件时,并没有任何 Request 和 Response 的封装,你能拿到的是一个 Node.js 中原始的 Socket 对象,所以当你要返回数据的时候需要自己按照 HTTP 返回数据包的格式来输出。

这个时候再挥起你的小手试一下 CURL 吧:

$ curl 'http://127.0.0.1:5555/d d' -v
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5555 (#0)
> GET /d d HTTP/1.1
> Host: 127.0.0.1:5555
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection 0

如愿以偿地输出了 400 状态码。

引申

接下来我们要引申讨论的一个点是,为什么这货不是一个真正意义上的 Bug。

首先我们看看 Nginx 这么实现这个黑科技的吧。

Nginx 实现

打开 Nginx 源码的相应位置

我们能看到它的状态机对于 URI 和 HTTP 协议声明中间多了一个中间状态,叫 sw_check_uri_http_09,专门处理 URI 后面的空格。

在各种 URI 解析状态中,基本上都能找到这么一句话,表示若当前状态正则解析 URI 的各种状态并且遇到空格的话,则将状态改为 sw_check_uri_http_09

case sw_check_uri:
  switch (ch) {
  case ' ':
    r->uri_end = p;
    state = sw_check_uri_http_09;
    break;

  ...
  }

  ...

然后在 sw_check_uri_http_09状态时会做一些检查:

case sw_check_uri_http_09:
    switch (ch) {
    case ' ':
        break;
    case CR:
        r->http_minor = 9;
        state = sw_almost_done;
        break;
    case LF:
        r->http_minor = 9;
        goto done;
    case 'H':
        r->http_protocol.data = p;
        state = sw_http_H;
        break;
    default:
        r->space_in_uri = 1;
        state = sw_check_uri;
        p--;
        break;
    }
    break;

例如:

  • 遇到空格则继续保持当前状态开始扫描下一位;
  • 如果是换行符则设置默认 HTTP 版本并继续扫描;
  • 如果遇到的是 H才修改状态为 sw_http_H认为接下去开始 HTTP 版本扫描;
  • 如果是其它字符,则标明一下 URI 中有空格,然后将状态改回 sw_check_uri,然后倒退回一格以 sw_check_uri继续扫描当前的空格。

在理解了这个“黑科技”后,我们很快能找到一个很好玩的点,开启你的 Nginx 并用 CURL 请求以下面的例子一下它看看吧:

$ curl 'http://xcoder.in:5555/d H' -v
*   Trying 103.238.225.181...
* TCP_NODELAY set
* Connected to xcoder.in (103.238.225.181) port 5555 (#0)
> GET /d H HTTP/1.1
> Host: xcoder.in:5555
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Server: openresty/1.11.2.1
< Date: Tue, 12 Dec 2017 11:18:13 GMT
< Content-Type: text/html
< Content-Length: 179
< Connection: close
<
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>openresty/1.11.2.1</center>
</body>
</html>
* Closing connection 0

怎么样?是不是发现结果跟之前的不一样了——它居然也返回了 400 Bad Request。

原因为何就交给童鞋们自己考虑吧。

RFC 2616 与 RFC 2396

那么,为什么即使在 Nginx 支持空格 URI 的情况下,我还说 Node.js 这个不算 Bug,并且指明 Nginx 是“黑科技”呢?

后来我去看了 HTTP 协议 RFC。

原因在于 Network Working Group 的 RFC 2616,关于 HTTP 协议的规范。

在 RFC 2616 的 3.2.1 节中做了一些说明,它说了在 HTTP 协议中关于 URI 的文法和语义参照了 RFC 2396

URIs in HTTP can be represented in absolute form or relative to some known base URI, depending upon the context of their use. The two forms are differentiated by the fact that absolute URIs always begin with a scheme name followed by a colon. For definitive information on URL syntax and semantics, see “Uniform Resource Identifiers (URI): Generic Syntax and Semantics,” RFC 2396 (which replaces RFCs 1738 and RFC 1808). This specification adopts the definitions of “URI-reference”, “absoluteURI”, “relativeURI”, “port”, “host”,“abs_path”, “rel_path”, and “authority” from that specification.

而在 RFC 2396中,我们同样找到了它的 2.4.3 节。里面对于 Disallow 的 US-ASCII 字符做了解释,其中有:

  • 控制符,指 ASCII 码在 0x00-0x1F 范围内以及 0x7F;

    控制符通常不可见;

  • 空格,指 0x20;

    空格不可控,如经由一些排版软件转录后可能会有变化,<span style=“color: #ccc;”>而到了 HTTP 协议这层时,反正空格不推荐使用了,所以就索性用空格作为首行分隔符了;</span>

  • 分隔符,"<"">""#""%""\""

    #将用于浏览器地址栏的 Hash;而 %则会与 URI 转义一同使用,所以不应单独出现在 URI 中。

于是乎,HTTP 请求中,包体的 URI 似乎本就不应该出现空格,而 Nginx 是一个黑魔法的姿势。

小结

嚯,写得累死了。本次的一个探索基于了一个有空格非正常的 URI 通过 CURL 或者其它一些客户端请求时,Node.js 出现的 Bug 状态。

实际上发现这个 Bug 的时候,客户端请求似乎是因为那边的开发者手抖,不小心将不应该拼接进来的内容给拼接到了 URL 中,类似于 $ rm -rf /

一开始我以为这是 Node.js 的 Bug,在探寻之后发现是因为我们自己没用 Node.js HTTP Server 提供的 clientError事件做正确的处理。而 Nginx 的正常请求则是它的黑科技。这些答案都能从 RFC 中寻找——再次体现了遇到问题看源码看规范的重要性。

另,我本打算给 http-parser 也加上黑魔法,后来我快写好的时候发现它是流式的,很多状态没法在现有的体系中保留下来,最后放弃了,反正这也不算 Bug。不过在以后有时间的时候,感觉还是可以好好整理一下代码,好好修改一下给提个 PR 上去,以此自勉。

最后,求 fafa。

求 fafa

交流

如果你有更多的想法,或者想了解蚂蚁金服的 Node.js、前端以及设计小伙伴们的更多姿势,可以报名首届蚂蚁体验科技大会 SEE Conf,比如有死马大大的《Developer Experience First —— Techless Web Application 的理念与实践》,还有青栀大大的《蚂蚁开发者工具,服务蚂蚁生态的移动研发 IDE》等等。

报名官网:https://seeconf.alipay.com/

期待您的光临。

【北京、杭州】阿里巴巴天猫技术部 招聘高级前端开发工程师(急招!)

$
0
0

急招!急招!急招!

工作地点:北京、杭州邮箱:fuchuan.xfc@alibaba-inc.com


岗位描述 1、依据产品需求完成高质量的跨终端Web(PC+Mobile)/Node.js/Native App的前端开发和维护; 2、对具体的产品进行性能优化,实现极致的页面加载、执行和渲染时间; 3、在理解前端开发流程的基础上,结合前端实际建立或优化提升工作效率的工具; 4、在理解产品业务的基础上,提升产品的用户体验,技术驱动业务的发展; 5、关注前端前沿技术研究,通过新技术服务团队和业务。 岗位需求 1、精通各种前端技术,包括HTML/CSS/JavaScript/Node.JS等; 2、具备跨终端的前端开发能力,在Web(PC+Mobile)/Node.js/Native App三个方向上至少精通一个方向,具备多个的更佳,鼓励在Native和Web技术融合上的探索; 3、对前端工程化与模块化开发有一定了解,并有实践经验(如RequireJS/SeaJS/KISSY等); 4、至少熟悉一门非前端的语言(如Java/PHP/C/C++/Python/Ruby),并有实践经验; 5、具备良好的团队协作精神,能利用自身技术能力提升团队整体研发效率,提高团队影响力; 6、对前端技术有持续的热情,个性乐观开朗,逻辑性强,善于和各种背景的人合作。


我们的团队:积极向上,活力四射,氛围轻松,有愿景,有挑战。 随着阿里新零售战略的推进,新零售供应链平台事业部应运而生。新零售供应链平台事业部零售终端团队承担面向终端零售企业的技术输出重任。我们致力于为零售行业提供数据化、智能化的一整套系统解决方案,同时我们也要承担零售购物体验的创新和变革。无人店——淘咖啡技术方案的成功落地只是我们团队一个开始,未来我们会有更广阔的业务和技术的发展空间。 1.你将践行新零售的战略,帮助亿万零售企业完成新零售的转型。 2.你将赋能亿万零售企业完成数据化、互联网化的转型。 3.你将帮助亿万零售企业完成智能化、富体验的重构。 4.你将帮助亿万零售企业颠覆传统购物体验,重新定义客户,重新定义营销,重新定义零售。

好用的npmjs开源包

分享:基于vue写的cnode移动客户端

$
0
0

1.jpg2.jpg3.jpg5.jpg6.jpg7.jpg8.jpg

实现的功能有:

  • 首页
  • 用户登录,退出
  • 个人主页
  • 文章列表下拉刷新,上拉加载更多
  • 帖子详情页
  • 发帖,评论,点赞,收藏文章等

新手上路,请多关照。 项目地址


Node.js 性能优化的基本方法与实战

$
0
0

本文首次发表于北斗同构github, 转载请注明出处

前言

很多前端工程师在做页面性能调优的过程中,极少关注代码本身的执行效率,更多关注的是网络消耗,比如资源合并减少请求数、压缩降低资源大小、缓存等. 我并不觉得这不合理,相反,在很大程度上这是足够正确的做法,举个例子, JS本身的执行时间是30ms(毫秒),在动辄三五秒的页面加载时间中的占比实在太低了,就算拼了命把性能提升10倍,执行时间降到3ms,整体性能提升也微不足道,甚至在用户层面都无法感知. 因此去优化其它性能消耗的大头更加明智.

但从Node.js(服务端)的角度来看,JS本身的执行时间却变得至关重要,还是之前的例子,如果执行时间从30ms降到3ms, 理论上QPS就能提升10倍,换句话说,以前要10台服务器才能扛住的流量现在1台服务器就能扛住,而且响应时间更短.

那到底Node端如何做性能优化呢?

方法

有两种方法,一种是通过Node/V8自带的profile能力 , 另一种是通过alinode的 CPU profile功能. 前者只列出了各函数的执行占比, 后者包括更加完整的调用栈,可读性更强,更加容易定位问题,建议采用后者.

方法1: Node 自带 profile

  • 第1步: 以–prof参数启动Node应用
$ node --prof index.js
  • 第2步: 通过压测工具loadtest向服务施压
$ loadtest  http://127.0.0.1:6001 --rps 10
  • 第3步: 处理生成的log文件
$ node --prof-process isolate-0XXXXXXXXXXX-v8-XXXX.log > profile.txt 
  • 第4步: 分析profile.txt文件

profile.txt文件如下图,包括JS和C++代码各消耗多少ticks, 具体分析方法详见node profile文档

方法2: alinode的CPU profile

  • 第1步: 安装alinode

alinode是与 Node 社区版完全兼容的二进制运行时环境, 推荐使用tnvm工具进行安装

$ wget -O- https://raw.githubusercontent.com/aliyun-node/tnvm/master/install.sh | bash

完成安装后,需要将tnvm添加为命令行程序. 根据平台的不同,可能是~/.bashrc,~/.profile 或 ~/.zshrc等

$ source ~/.zshrc

以alinode-v3.8.0为例, 对应node-v8.9.0, 下载该版本并启用它

$ tnvm install alinode-v3.8.0
$ tnvm use alinode-v3.8.0

  • 第2步: 用安装的alinode运行时启动应用
$ node --perf-basic-prof-only-functions index.js
  • 第3步: 通过压测工具loadtest向服务施压
$ loadtest  http://127.0.0.1:6001 --rps 10
  • 第4步: cpu profile

假设启动的worker进程号为6989, 执行以下脚本, 三分钟后将在/tmp/目录下生成一个cpuprofile文件/tmp/cpu-profile-6989-XXX.cpuprofile脚本详见take_cpu_profile.sh

$ sh take_cpu_profile.sh 6989
  • 第5步: 将生成的cpuprofile文件导入到Chrome Developer Tools进行分析

cpu profile img

实战

下面通过一个真实的案例展示如何一步步地做性能调优.

通过loadtest请求1000次,统计平均RT, 初始RT为15.8ms

origin

剔除program和GC消耗,性能消耗的前三位分别是get,J_eval三个方法

展开最耗性能的get方法调用栈,可以定位到get方法所在的位置,具体代码如下

{
    key: 'get',
    value: function get(propName) {
      if (!this.state[propName]) {
        return null;
      }
      return JSON.parse(JSON.stringify(this.state[propName]));
    }
  }

方法体中,JSON.parse(JSON.stringify(obj))虽然使用便捷,但却是CPU密集型操作. 做一次验证,去除该操作, 直接返回this.state[propName]. RT时间降为12.3ms了

这仅仅是一次试验,肯定不能直接移除JSON.parse(JSON.stringify(obj)), 不然会影响业务逻辑的. 参考下常用拷贝方法的性能对比, 自配梯子. 截图如下:

其中性能最优的是lodash deep clone,采用该库替换,再验证一遍, RT降为12.8ms

第二耗性能是的J方法,里面大部分是各个组件的render时间,暂时略过,以同样的方式对_eval方法进行一次优化, RT降为10.1ms.

以此类推,根据CPU profile找出性能消耗的点,逐个去优化.

小白提个问,用mongodb存sessionid

$
0
0

用mongodb存sessionid的情况下, 是要每次在处理请求的时后用取到cookie的id值去mongodb里相应存sessionid的集合中找有没有值,来判断有没有登陆吗

性能优化知识与实践整理

$
0
0

Web性能

从底层计算机网络协议到应用层各个方面去理解Web性能。目前项目处于刚开始阶段,欢迎对性能优化感兴趣的同学共同参与总结!

Github: https://github.com/laoqiren/web-performance

目录

引用说明

此项目会引用许多其他文章书籍的图片或部分内容,我会尽量都加以注明,如果有部分遗漏以致于侵犯到您的版权,烦请联系我修改!此项目旨在整理零碎的知识和实践方案,方便交流学习,请勿用于商业用途。

参与贡献

  • 提想法和建议
  • 纠错完善
  • 增加新章节或内容

node 有没有做全文搜索的解决方案

$
0
0

类似 java 的solr,php 的 sphinx…

关于async,异步调用的问题?

$
0
0

我用了koa2 的node框架写项目,全部用es6的语法,是边学习边es6边写项目,练手项目嘛! 然后。查询数据库我用了async 异步去查询并return,然后var userinfo = await dbquery.queryUser([137****070]);下面写 ctx.body = {title: ‘koa2 json’}, ctx.body = {title: ‘koa2 json’} 竟然会无效? 求解:

开源,想说爱你不容易!

$
0
0

去年年末的时候,我心血来潮,想搞一个side project,闲暇之余饶有兴趣的做个项目练练手,没有想那么多,于是向团队征求了项目的方向,大家建议我做接口管理平台,OK,操起久违的vue和node,撸起袖子先干起来,产品第一版出来后,大家感觉不错,其中有一位顺口对我说道:昕哥,你去GitHub开源吧,涨涨人气呗,对你的项目也有利!我一听,得嘞,顺手就把源码传了上去。顺便也在几个技术群发了发,没过多久,github的star数不知不觉的涨了起来,直到有一天有一位朋友给我发邮件请教我有关产品部署的问题,我突然意识到,这个项目是不是还有点搞头。

于是,我建了一个群,拉了几个人进去,工作的闲暇之余也在不断的完善着这个项目,虽然不是很确定,但是冥冥之中我感觉到这个项目我不会轻易的放弃,直到有一天,一家比较大的公司找到我,他们跟我说想为这个产品做定制化,收费的那种,我突然脑光一闪,难道我要凭这个项目做上CEO,迎娶白富美(等等,这个还是算了),走上人生巅峰了! 打住!现实没有我想的那么好,在和那家公司深入交流之后,他们放弃了,他们觉得我的这个项目目前还不是很完善,口碑也没有真正的建立起来。恩!没事,这对我来说是一种动力,产品不完善说明产品的潜力还很大,口碑的话说明产品的宣传还不足。这下我更有干劲了,在无数个不眠之夜,我尽我所能发布了无数“激动人心”的新版本,群的人数也在不断增加,Github上star的飙升更是我的强心剂!那种比每个月发工资更刺激的成就感是难以形容的!

同时我也看了看市面上的竞品,很多都是闭源收费的,再看看自己的项目,发现没比他们差哪里啊,心想:看老子开源不干死你!于是,我从一开始纯粹的side project渐渐的想把它做成一个能在市面上立足,有点成绩的产品。于是,我比之前更专心的去听群里的建议,更频繁的去更新版本,更积极的去做产品的宣传推广!我要用开源的共享和自由精神去打败我的竞品们! 群里有一位朋友找我说,我这边有一个功能想做,我给你点钱,你抓紧把这个功能做了吧,我看了看这个功能,发现蛮简单也挺通用的,于是说要啥钱啊,免费下一版本把这个功能上上去,那个朋友开心的合不拢嘴,我也很开心!群里还有一个朋友找我,说他们公司销售的产品想内嵌我的产品,问我有没有什么限制,我很爽快的说没有啊,很欢迎啊,这不是给自己涨粉嘛,那个朋友有点疑惑的问我那你这个产品怎么盈利呢,我说我开源啊,用户数很快就会起来的,有了用户到时候钱自然而然就来了,那个朋友笑的很开心,我也很开心!

直到有一天,我发现我的产品功能越做越强大,但是群里的朋友对产品却越来越挑剔了,他们经常会给我提很多各种各样的需求,我加班加点的满足这些需求后他们还是不能满意,更让我不解的是,有一些原本我的用户情愿投向竞品,去付费使用他们的产品!

看着github上满满的issue我有点迷茫和疲惫了,我究竟在干嘛,我到底为了什么呢?为了那些star?为了很多人使用我产品的满足感?还是仅仅因为自己的不甘心!

我有时候也曾想,万一我哪天闭源了呢,万一我哪天收费了呢,这些用户还会追随这个产品嘛,我不敢细想,因为换位思考下,换做是我我应该是不会的了!当你当初纯粹的开源热情已渐渐熄灭的时候,当你面对更多生活压力的时候,当越来越现实的商业问题迎面而来的时候,是什么支撑你把开源这条路走下去的呢?

是责任,更是信念!我不确定,但是心底的一个声音这么告诉着我,当前这个平台已经承载了上万个用户,数十万个接口数据,如果我现在放弃,我可以很轻松,但是这些用户怎么办,这些用户数据怎么办,如今,接口对于很多互联网公司来说已经变得越来越重要,它不像其他娱乐休闲类的服务可有可无,上面凝聚了太多互联网人员的心血和汗水,做为一个互联网人,我的产品一定要配得上这些心血和汗水,要够格!那么我该如何够格呢,所以信念就是我的产品要成为业界的NO.1.

我不仅会把开源之路走到底,我更要去整合开发流程的中后阶段,为用户提供一个解决方案,让接口去驱动我们的开发,天道酬勤,秉承着这一信念和努力,我也找到了志同道合的伙伴们和相信我的投资者,让我可以出来真正的去做自己想做的事情,这个世界很大,有70多亿的人口,哪怕只有现如今1万多个用户在使用这个产品,那也是对我信念的延续,只要生活还能继续,我就会坚定的把这条路走下去!

11月份,我也去了趟北京,和很多朋友交流了关于这个产品的想法,非常感谢BeeCloud的黄总给了我很多商业上的建议,也感谢滴滴,58的伙伴们给了我很多产品和技术上的帮助与支持,在这个寒冷的冬天,我感觉整个城市都温暖了起来。 国外有很多优秀的开源产品,而国内往往都是大厂在推动,最后往往沦为kpi的产物,不是国内技术不行,而是现实中大家太忙碌,在这样一个大家为了生计忙碌得失去信仰的社会,我们是否应该停下来找一下,究竟是什么让我真正的自由和快乐。

开源,想说爱你不容易!

感谢您看到了最后!对了,这个产品叫DOClever!

使用sequelize操作数据库,后期如何新增字段问题

$
0
0

请问一下,使用sequelize来操作数据库,但是项目进行到后期肯定会有字段的新增,那么这个时候一般都是怎么操作的呢,是自己手动去数据库新增,然后修改sequelize定义的模型么,还是有其他的方法


webpack+react+express+node搭建的网站合理吗?

用 easy-json-schema 代替 json-schema 吧

$
0
0

介绍

JSON-SCHEMA 是一种基于 JSON 格式定义 JSON 数据结构的规范,有如下特性:

  1. 描述现有数据格式。
  2. 干净的人类和机器可读的文档。
  3. 完整的结构验证,有利于自动化测试。
  4. 完整的结构验证,可用于验证客户端提交的数据。

json-schema 演示

{
    "type": "object",
    "properties": {
        "id": {
            "type": "integer"
        },
        "name": {
            "type": "string"
        },
        "price": {
            "type": "number",
            "minimum": 0,
            "exclusiveMinimum": true
        }
    },
    "required": ["id", "name"]
}

Why

作者为什么开发一个 easy-Json-schema 工具呢,就是为了简化 json-schema 定义,大家看看上面的 json 定义,一个很简单结构的 json 用了非常多的字段定义,书写起来非常麻烦。

easy-json-schema

如果用 easy-json-schema 定义上面的 json结构,是非常清晰和易用的。

{
    "*id": "integer",
    "*type": "string",
    "price":{
        "type": "number",
            "minimum": 0,
            "exclusiveMinimum": true
    }
}

感兴趣的朋友可以关注下

github: github.com/easy-json-schema

在线演示: easy-json-schema.github.io

Create-react-app+Antd+Less配置

$
0
0

说明

React官方脚手架工具Create-react-app 用于快速创建React应用,但依旧有局限性,我们根据项目需求需要对Create-react-app的配置进行修改。这里针对引入Antd的两种配置方式进行配置。

方案

一. React-app-rewired(一个对 create-react-app 进行自定义配置的社区解决方案)。

  1. 安装react-app-rewired

npm install --save-dev react-app-rewired

  1. 修改package.json启动项
/* package.json */
"scripts": {
   "start": "react-app-rewired start",
   "build": "react-app-rewired build",
   "test": "react-app-rewired test --env=jsdom",
}
  1. 在项目根目录创建一个 config-overrides.js 用于修改默认配置。
module.exports = function override(config, env) {
  // do stuff with the webpack config...
  return config;
};
  1. 使用babel-plugin-import实现Antd按需加载,修改config-overrides.js

npm install --save-dev babel-plugin-import

/* config-overrides.js */
const { injectBabelPlugin } = require('react-app-rewired');
module.exports = function override(config, env) {
    config = injectBabelPlugin(['import', { libraryName: 'antd', style: 'css'}], config);
    return config;
};

5.使用react-app-rewire-less配置Less

npm install --save-dev react-app-rewire-less

/* config-overrides.js */
const { injectBabelPlugin } = require('react-app-rewired');
const rewireLess = require('react-app-rewire-less');

module.exports = function override(config, env) {
   config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
   config = rewireLess.withLoaderOptions({
     modifyVars: { "@primary-color": "#1DA57A" },
   })(config, env);
    return config;
};

我遇到的问题:

  1. _DEV_ is not defined.

npm install --save-dev react-app-rewire-defind-plugin

/* config-overrides.js */
const { injectBabelPlugin } = require('react-app-rewired');
const rewireLess = require('react-app-rewire-less');
const rewireDefinePlugin = require('react-app-rewire-define-plugin');

module.exports = function override(config, env) {
    config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
    config = rewireLess.withLoaderOptions({
        modifyVars: { "@primary-color": "#1DA57A" },
    })(config, env);
    config = rewireDefinePlugin(config, env, {
        __DEV__: false
    });
    return config;
};
  1. Cannot read property ‘exclude’ of undefined 参考 https://github.com/timarney/react-app-rewired/issues/145解决方案 https://github.com/dawnmist/react-app-rewired/commit/25208ab81791edb4356dc959188bcd4c4471ad87

二. npm run eject 暴露所有内建的配置

  1. 使用babel-plugin-import实现Antd按需加载,修改package.json,或者在项目根目录新建文件.babelrc写配置,注意是二选一。

npm install --save-dev babel-plugin-import

1)package.json

"babel": {
    "presets": [
      "react-app"
    ],
    "plugins": [
      [
        "import",
        {
          "libraryName": "antd",
          "style": true
        }
      ]
    ]
  },

2).babelrc

{
   "presets": [
      "react-app"
    ],
    "plugins": [
      [
        "import",
        {
          "libraryName": "antd",
          "style": true
        }
      ]
    ]
}

注意: 不要认为package.json里已有presets配置这里就不用写,这里的.babelrc会覆盖package.json里带有的babel配置,如果删除presets配置,会报错。

  1. 引入Less

1)安装less-loader 和 less

npm install --save-dev less-loader less

2)修改config文件夹下的webpack.config.dev.js和webpack.config.prod.js文件(都需要修改) 查找 :exclude原本的 exclude: [/\.js$/, /\.html$/, /\.json$/],修改为 exclude: [/\.html$/, /\.(js|jsx)$/, /\.(css|less)$/, /\.json$/, /\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],

查找:test: /.css$/原本的 test: /\.css$/,修改为 test: /\.(css|less)$/,

在这个test的下面找到use,添加loader

  use: [  
    {...},
    {...},
    {
      loader: require.resolve('less-loader') // compiles Less to CSS
    }
  ],

ok,以上两种方式修改配置,择优选取。 如果引入Antd,可能依赖于引入Less, 如果不想引入Antd,可以舍弃Antd部分,按步骤引入Less。

https://github.com/zhaoyu69/antd-spa包含第一种方式 https://github.com/zhaoyu69/nodejs-spider包含第二种方式

入门到放弃node系列之Hello Word篇

$
0
0

前言

本文首发公众号【一名打字员】

入门到放弃之node系列终于新鲜出炉了,这个系列覆盖了从基础到进阶的基本知识与方向,适合入门的打字员们,系列结束后你可以建立一个自己的网站,写一个自己的服务端应用或者更多。系列中大都是打字员们口口相传的言论,不代表官方观点。

本系列环境 MAC OS 10.12.6,node v8.0,npm 5.0.4.

背景

众所周知Node.js是一个基于V8引擎的JavaScript运行环境,由Ryan Dahl开发。它使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。关于node的详细在这里就不做多的介绍,大家可以在wiki或者在搜索引擎中查看更多详细介绍。

大家只需要知道基于node,可以轻松的写出高性能的WEB服务器,也能够写出提高工作效率的工具,实在是前端人员的福音。

NPM

在进行下一步之前不得不介绍一下npm,node拥有一个强大的生态圈,npm则是全球最大的开源库生态系统。它是一个包管理器,能在代码部署上解决很多问题。 通常我们可以这样使用NPM。

  1. 下载别人开源的第三方包到本地使用。

  2. 将自己编写的包开源供别人使用。

我们可以通过nom install argv来安装node应用中所需要的模块。使用npm init初始化项目。

安装

关于node的安装这里不多费口舌,我们可以进入node的中文官网下载最新版本,然后进行安装。MAC中一般会自带node环境,Linux和Windows下均需要自行下载编译安装。

当我们输入node -v能够打印出node的对应版本信息时,代表已经成功安装node。

HelloWord

是的,令人激动的helloword要开始了,每当我们接触到新的语言时,我们通常写的第一段代码就是HelloWord。我们下载好node之后如何运行呢,我们可以新建一个js文件,在里面写上以下内容: untitled1.png

运行node helloword.jsuntitled2.png

是不是看到了熟悉的Hello Word。 是的,你没有看错,这就是你熟悉的js语法,和你平常写的代码一毛一样,但是你却可以直接使用命令交互模式调试js代码片段。这样无论你是前端写写js特效、flash写脚本效果、untity3D脚本游戏打字员(请允许我这么称呼)或者其它打字员,你都可以方便的使用起来。 

基本概念

学习node我们需要掌握以下几个概念:

  1. 模块 通常我们写的应用程序都会比较大,我们会将其进行模块化,在node中我们可以将代码进行整理,放在不同的文件中,每一个文件就是一个模块,路径名称就是模块名。我们可以使用require来导入其它模块,也可以使用ES2015的语法import。如下所示:
var func1= require('./func');
var func2 = require('./func.js');
var func3 = require('/home/mrpan/func');
var func4 = require('/home/mrpan/func.js');
//当然我们也可以这样引入一个json文件
var data = require('./data.json');

通常我们可以使用export来到出一个模块对象的公有方法和属性,下面我们把刚才的helloword方法导出一下。

exports.sayHello(){
    console.log('Hello World!');
}

通常我们使用module对象可以访问到当前模块的一些相关信息,但它最多的用途是替换当前模块的导出对象。如下所示:

module.exports = function () {
    console.log('Hello World!');
};

2.包 从上面的知识我们大概知道了node里面模块就是一个个的js,然后多个模块则组成了一个包。在一个包的所有子模块中,通常我们需要一个入口模块,入口模块的导出对象被作为包的导出对象。如下图,则是一个标准的node程序模块图:

untitled3.png

其中index.js则是入口文件。另外,当模块的文件名是index.js,加载模块时可以使用模块所在目录的路径代替模块文件路径。如果想自定义入口模块的文件名和存放位置,就需要在包目录下包含一个package.json文件,并在其中指定入口模块的路径。 其内容一般如下:

{
  "name": "helloword",
  "version": "1.0.0",
  "description": "node test",
  "main": "./index.js",
}
  1. 工程目录 看完上面的童鞋一定已经被绕晕了,完全不知道我们的工程目录结构一般到底是咋样得了。在这里重申一次,一个简单明了的目录,能够帮助开发人员更方便的阅读源码。
- /home/user/workspace/node-echo/   # 工程目录
    - bin/                          # 存放命令行相关代码
        node-echo
    + doc/                          # 存放文档
    - lib/                          # 存放API相关代码
        echo.js
    - node_modules/                 # 存放三方包
        + argv/
    + tests/                        # 存放测试用例
    package.json                    # 元数据文件
    README.md                       # 说明文件

结语

通过上面的文章,大家应该已经知道了基础的node知识,以及能够编写并执行简单的应用了。接下来希望想学的童鞋们赶紧去恶补一下js的基础语法,下一次我们将会讲述在node中的网络操作。

另外,有童鞋问本猿有没有做过什么应用可以开源出来让大家学习的,在这里贴出两个项目:

  1. FullStack --基于node.js的express模块编写的全干社区 原本是想做一个社区的,现在暂时用来做个人网站.
  2. webwx-api– 网页版的微信API(node版)

请问大佬们,我这样的前端可以找到好工作吗?

$
0
0

做了2年前端,第一年坚持用jq和原生方式来写代码,css和html都是手写,很少用过第三方ui框架。去年6月份开始接触nodejs。跟着资料把process,fs等模块api都跑了一遍。用express写过一个小博客。今年3月份开始用vue做开发。5月份花了一点时间把koa2源码看了一边,基本理解了内部工作原理。

从7月分开始使用egg做项目,为了理解mvc的概念,顺便把egg的源码看了一变。了解了egg的内部基本原理(基本上都是一行一行去看,写注释)。准备到年底重构一一遍egg框架 webpack等工具也学习过,但是现在不经常看都快忘记了。使用过mongodb做过一个mock系统,用来造假数据给前端使用。

貌似我都是学而不精。学了很多,忘了很多。期间用vue写了个可定制的app报表模板。

明年准备看下算法方面知识。

貌似我这样的前端,前后端都懂一点,都不精通,最近比较迷茫。请教有经验的大佬们给小点一点指引。感激!!

Viewing all 14821 articles
Browse latest View live