V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
monkeyNik
V2EX  ›  分享创造

用 C 语言手撸了一个 HTTP 服务器,求关注

  •  
  •   monkeyNik ·
    Water-Melon · 2023-10-14 09:51:58 +08:00 · 2173 次点击
    这是一个创建于 410 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好,近来用 C 语言从头写了一个 HTTP 服务器。

    这个服务器有点类似于 openresty 和 PHP 的混合体,它的特性如下:

    • 使用脚本语言处理每个请求
    • 脚本解释器与 HTTP 收发(即 HTTP 服务器)处于同一个线程
    • 每个处理请求的脚本有完全隔离的运行环境
    • 当一个请求的处理不慎陷入死循环时,不会影响其他请求的处理,也不会影响新建 TCP

    前两点有点类似 openresty ,第三点有点类似 PHP 的运行环境。

    不过因为时间比较短,因此也有一些限制和不完善的地方,例如:

    • 暂时只支持 HTTP 1.x
    • 暂时只支持短链接
    • 暂不支持 chunk mode

    给出一个示例演示一下。

    我们可以使用如下脚本处理请求:

    //entry.m
    /*
     * Implement a simple controller.
     * There are three variable injected in this script task:
     *   1. Req. Its prototype is:
     *       Req {
     *           method; //string  e.g. GET POST DELETE ...
     *           version; //string e.g. HTTP/1.0 HTTP/1.1
     *           uri; //string e.g. /index/index
     *           args; //an key-value array, e.g. ["key":"val", ...]
     *           headers; //an key-value array, e.g. ["Content-Type":"application/json", ...]
     *           body; //string
     *       }
     *
     *    2. Resp. Its prototype is:
     *        Resp {
     *            version; //same as Req's version
     *            code; //integer  e.g. 200
     *            headers; //same as Req's headers
     *            body; //same as Req's body
     *        }
     *
     *.   3. Basedir. A string of the base directory path. (Not used in this example)
     */
    
    #include "@/index.m"
    
    str = Import('str');
    sys = Import('sys');
    
    uri = str.slice(Req.uri, '/');
    uri && (ctlr = str.capitalize(uri[0]), o = $ctlr);
    if (!o || sys.has(o, uri[1]) != 'method') {
      Resp.code = 404;
    } else {
      o.__action__ = uri[1];
      Resp.body = o.__action__();
      Resp.headers['Content-Length'] = str.strlen(Resp.body);
    }
    

    上面这个脚本简单来说,就是实现了一个简单的控制器( MVC 中的 C )。

    下面这段代码用来处理对应 URI 的请求。

    //index.m
    
    Json = Import('json');
    
    Index {
        @index() {
            Resp.headers['Content-Type'] = 'application/json';
            return Json.encode(['code': 200, 'msg': 'OK']);
        }
    }
    

    然后启动程序:

    medge -p 8080
    

    最后使用 curl 测试一下:

    $ curl -v -H "Host: test.com"  http://127.0.0.1:8080/index/index
    *   Trying 127.0.0.1:8080...
    * Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
    > GET /index/index HTTP/1.1
    > Host: test.com
    > User-Agent: curl/7.81.0
    > Accept: */*
    > 
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    < Content-Length: 23
    < Content-Type: application/json
    < 
    * Connection #0 to host 127.0.0.1 left intact
    {"code":200,"msg":"OK"}
    

    项目的 Github 仓库:Medge

    感兴趣的小伙伴还望不吝你的小星星~

    感谢大家!

    sadfQED2
        1
    sadfQED2  
       2023-10-14 10:40:30 +08:00 via Android
    跟 openresty+lua 比有啥优势?
    monkeyNik
        2
    monkeyNik  
    OP
       2023-10-14 10:54:50 +08:00 via iPhone
    @sadfQED2 其实适用的场景就不一样,也谈不上优势了。提及 openresty 的原因是将脚本与服务器整合的想法一样。特性上的不同就是,这个东西的脚本不会出现类似_G 那种多请求间存在共享的问题,也不会出现单请求死循环导致其他所有请求都裂开的问题
    sadfQED2
        3
    sadfQED2  
       2023-10-14 11:17:40 +08:00 via Android
    @monkeyNik 你这个定位是拿来写业务逻辑的还是写网关逻辑?写网关逻辑我觉得多请求共享应该是优势才对,方便资源共享,减少消耗。
    monkeyNik
        4
    monkeyNik  
    OP
       2023-10-14 12:49:45 +08:00 via iPhone
    @sadfQED2 不是网关逻辑,就是一些 web API 开发类的
    WinterWu
        5
    WinterWu  
       2023-10-15 20:29:05 +08:00
    如果是做业务逻辑,核心是写后端逻辑才是更复杂的事情。
    monkeyNik
        6
    monkeyNik  
    OP
       2023-10-16 09:32:20 +08:00 via iPhone
    @WinterWu 是的,逻辑可以使用脚本来完成 脚本支持了常用的库以及和数据库通信的部分
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5558 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 07:49 · PVG 15:49 · LAX 23:49 · JFK 02:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.