Mochiweb的设计分析

 

Mochiweb的设计分析

March 15th, 2009 :: refactor

转自:http://erlang-china.org/misc/mochiweb-inside.html

Web服务器的基本工作大致分3步: 

  1. 接收HTTP请求; 
  2. 处理HTTP请求,生成响应内容;
  3. 发送响应

一、处理请求和发送响应 

模块mochiweb_request可说是Mochiweb处理HTTP请求的核心部分,它总共负责了第2步和第3步工作。因此参数化模块mochiweb_request的实例不像它的模块名那样单纯:它还负责将请求的响应(通过Socket连接,调用gen_tcp:send)发还(response)给浏览器。这个模块是一个参数化模块。这意味着它具有基于对象(Object-based)的特点,它的每个实例化对象代表一个用户请求,用户可以像OO那样操作HTTP请求:获取请求信息,生成响应内容等。 

对静态内容(如html文件,静态图片)来说,无非是读取文件系统中静态文件的内容作为响应消息体(Body)发送给客户端浏览器。 

对动态内容来说,生成响应内容的业务逻辑是用户通过编程实现的:解析请求(包括URL路径和HTTP请求头),然后生成相应的响应内容。用户编写业务逻辑处理函数,然后将函数插入到mochiweb中。此函数带有一参数,该参数是参数化模块mochiweb_request的实例(假设为Req),在这个定制函数中可以通过这个Req实例对象获取浏览器请求的所有信息(包括URL路径和HTTP请求头),处理后的响应数据也通过Req实例提供的函数发还给浏览器,有好几种方式: 

  1. 通过Req实例对象直接发还给浏览器

     

    a) 通过这个Req实例对象的response/1函数直接发还给浏览器:
    Req:response({Code, ResponseHeaders, Body})
    这个函数接收一个tuple参数,参数的第一个元素是HTTP状态码(比如200),第二个元素是响应消息头列表(一个二元tuple,key和value都是字符串,如{“Content-Type”, “image/jpeg”}),第三个元素即为HTTP响应内容;如果是动态内容,采用response函数直接发还响应数据还是比较方便的。

    b) 如果是静态文件,通过Req的serve_file(…)函数可以直接将文件发还给浏览器,告诉此函数文件的Web根目录(doc_root)和相对路径就可以将指定的静态文件发给浏览器了。doc_root目录一般在配置文件中设置,这个目录下的所有文件都可以通过HTTP访问。

    c) 一些常见的例行响应:
    不存在的URL请求返回404错误,可以直接调用Req:not_found() 若一切正常,调用Req:ok({…, Body})直接返回状态码为200的HTTP响应
    b,c两种情况的内部其实还是调用的Req:response()

  2. 通过Response模块对象将响应发还给浏览器

     

    Req:response函数会返回一个mochiweb_response参数化模块实例对象(假设为Response),Response实例对象包含有对应的Req实例对象。通过Response对象可以得到响应的相关信息(如响应状态码,响应消息头,对应的Req对象),它还有一个send函数可以将响应数据发还给浏览器(它的实现其实还是调用Req对象的send函数进行的)。Response之所以还要有send函数是为了发送chunked数据(HTTP 1.1)的方便,在第一次响应完成后,后继的chunk数据就可以通过最初返回的Response对象继续进行发送了,为此Response有个函数write_chunk()专门干这事,write_chunk检查了请求消息头中的HTTP 版本消息后就调用Response:send。

    因此,响应内容最终都是由参数化模块mochiweb_request的response/1函数发送的。而这个response(…)函数内部最后调用了Req:send(Data)函数将响应通过socket连接(调用gen_tcp:send)返还给浏览器,这一发送过程又分成两个阶段:响应消息头(Headers)的发送和消息体(Body)的发送,这两步都是通过Req:send完成的。

小结:对于程序员来说,他编写的服务器端处理HTTP请求的函数必须带有一个参数,这个参数代表了mochiweb_request参数化模块的一个实例对象。通过这个实例程序员可以得到HTTP请求的所有信息(包括路径、请求参数以及HTTP请求消息头),然后生成响应,他还可以通过该mochiweb_request实例对象将响应数据发还给浏览器。 

  • 如果响应的是静态文件,可以通过Request:server_file()函数发送响应;
  • 如果响应是动态生成的内容,通过Request:response()函数发送响应数据(文本数据和二进制数据都可以);
  • 如果是chunk响应,第一次调用Request:response()函数发送数据后,该函数会返回一个Response实例对象(参数化模块mochiweb_response的一个实例),以后的响应数据可以通过这个Response实例对象(调用Response:write_chunk(Data))发送后续的响应数据。

二、Web服务器的业务处理逻辑如何嵌入到Mochiweb 

业务处理逻辑被程序员编写成一个函数(函数式编程中函数也是一种数据,以下称为HttpLoop),处理函数HttpLoop是作为mochiweb启动时的参数之一进入Mochiweb,Mochiweb是这样启动的: 

mochiweb_http : start ([ { loop HttpLoop },... ])   

Request实例对象最终要调用gen_tcp:send(Socket,…)将响应数据由socket连接发给浏览器,因此,Request实例对象应该持有HTTP请求的socket连接。这个目标是这样实现的: 
在启动过程中,mochiweb_http又对用户的HttpLoop函数进行了重新包装 

Loop  =  fun   ( Socket )  ->
    
mochiweb_http : loop ( Socket HttpLoop )
end ,

每个浏览器连接请求对应着一个Socket连接,新的Loop函数以此Socket作为参数,然后通过mochiweb_http:loop函数对Socket连接和用户自定义的HttpLoop函数进行了处理,简单的说,这个函数做了如下工作: 

  1. 将Socket连接设置成能处理HTTP数据包 (inet:setopts(Socket, [{packet, http}]));
  2. 准备接收socket连接传来的数据(gen_tcp:recv(Socket,…));

当收到数据时:

  1. 将HTTP消息头数据提出(成为{HeaderName, HeaderValue}的tuple列表)
  2. 生成一个参数化模块mochiweb_request的实例对象,并将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表包装到此实例对象中,
  3. 然后调用用户的HttpLoop对请求进行处理和响应(如前所述,处理得到的响应数据也在用户编写的HttpLoop函数中被发送给浏览器)
  4. 最后根据HTTP请求信息决定是简单的关闭Socket连接,还是清理一下Req对象并保持连接(例如对keep-alive,chunk等类型的HTTP请求,以及还没完成数据传送的HTTP请求),以便继续让HttpLoop函数进行处理,

这都是在headers函数中进行的,还是看mochiweb_http模块的代码: 

  1. headers(SocketRequestHeadersHttpLoopHeaderCount) -> 
  2.     case gen_tcp:recv(Socket0, ?IDLE_TIMEOUT) of 
  3.         {okhttp_eoh} -> 
  4.             inet:setopts(Socket[{packetraw}])
  5.             % 将Socket连接、HTTP请求信息(路径、请求方法、HTTP版本)以及请求消息头列表打包成参数化模块(mochiweb_request)的实例对象 
  6.             Req = mochiweb:new_request({SocketRequest
  7.                                         lists:reverse(Headers)}),   
  8.             HttpLoop(Req)% 让用户编写的函数处理HTTP请求 
  9.            case Req:should_close() of 
  10.                 true ->  
  11.                     gen_tcp:close(Socket)
  12.                     exit(normal)
  13.                 false ->  
  14.                     Req:cleanup()
  15.                     mochiweb_http:loop(SocketHttpLoop) 
  16.             end
  17.         {ok, {http_header_Name_Value}} -> 
  18.             headers(SocketRequest[{
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用以下命令来查看RabbitMQ的版本: rabbitmqctl status | grep RabbitMQ 其中,rabbitmqctl是RabbitMQ的控制命令,status表示查看RabbitMQ的状态,grep RabbitMQ是用来过滤出包含RabbitMQ关键字的信息。执行该命令后,会输出类似以下的信息: Status of node 'rabbit@localhost' ... [{pid,1234}, {running_applications,[{rabbit,"RabbitMQ","3.8.9"}, {rabbit_common,[],"3.8.9"}, {rabbitmq_management,"RabbitMQ Management","3.8.9"}, {rabbitmq_web_dispatch,"RabbitMQ Web Dispatcher","3.8.9"}, {webmachine,"webmachine","1.10.3"}, {mochiweb,"MochiMedia Web Server","2.20.1"}]}, {os,{unix,linux}}, {erlang_version,"22.3.4.16"}, {memory,[{total,123456789}, {connection_readers,}, {connection_writers,}, {connection_channels,}, {connection_other,}, {queue_procs,}, {queue_slave_procs,}, {plugins,}, {other_proc,123456789}, {mnesia,}, {mgmt_db,}, {msg_index,}, {other_ets,}]}, {alarms,[]}, {listeners,[{clustering,25672,"::"},{amqp,5672,"::"}]}, {vm_memory_high_watermark,.4}, {vm_memory_limit,1073741824}, {disk_free_limit,50000000}, {disk_free,123456789}, {file_descriptors,[{total_limit,924}, {total_used,4}, {sockets_limit,829}, {sockets_used,2}]}, {processes,[{limit,1048576},{used,123}]}, {run_queue,}, {uptime,123456789}, {kernel,{net_ticktime,60}}] 其中,RabbitMQ的版本号为3.8.9。 ### 回答2: RabbitMQ是一种开源的消息中间件,它支持多个协议,包括AMQP(高级消息队列协议)和STOMP(可简单传输消息协议),可以用于构建分布式应用程序。在Linux系统上,使用以下命令可以查看RabbitMQ版本: 1. 使用rabbitmqctl命令 先使用sudo命令切换到root用户,然后输入以下命令: rabbitmqctl status 该命令将返回RabbitMQ服务器的状态信息,其中包括RabbitMQ服务器的本地版本信息。有时候,rabbitmqctl这个命令可能由于环境变量问题而无法找到。这种情况下,可以在命令前面加上sudo,以root用户的身份运行。 2. 使用rabbitmq-server命令 另一种查看RabbitMQ版本的方法是使用rabbitmq-server这个命令: sudo rabbitmq-server -version 该命令将返回RabbitMQ服务器的版本信息。 无论是使用rabbitmqctl还是rabbitmq-server命令,都可以方便地查看RabbitMQ版本信息。在开发和测试过程中,需要不断了解所采用的技术栈中各种软件和工具的版本信息,以便进行问题排查和性能优化。 ### 回答3: 在Linux系统中,我们可以通过多种方式来查看已安装的 RabbitMQ 版本。 1. 使用 rabbitmqctl 命令 rabbitmqctl 是 RabbitMQ 自带的命令行工具,我们可以通过执行以下命令来查看版本信息: ``` sudo rabbitmqctl status ``` 执行完命令后,会输出 RabbitMQ 的版本信息,如下所示: ``` Status of node rabbit@localhost ... [{pid,xxxx}, {running_applications, [{rabbitmq_management,"RabbitMQ Management Console","3.9.7"}, {rabbitmq_web_dispatch,"RabbitMQ Web Dispatcher","3.9.7"}, {rabbitmq_management_agent,"RabbitMQ Management Agent","3.9.7"}, {rabbitmq_auth_backend_ldap,"LDAP Authentication Backend","3.9.7"}, {rabbitmq_auth_mechanism_ssl,"SSL Authentication Mechanism","3.9.7"}, {rabbitmq_shovel_management,"Shovel Management Plugin","3.9.7"}, {rabbitmq_auth_backend_cache,"Caching Authentication Backend","3.9.7"}, {rabbitmq_mqtt,"RabbitMQ MQTT Adapter","3.9.7"}, {rabbitmq_recent_history_exchange,"RabbitMQ Recent History Exchange","3.9.7"}, {rabbitmq_consistent_hash_exchange,"RabbitMQ Consistent Hash Exchange","3.9.7"}, {rabbitmq_federation_management,"Federation Management Plugin","3.9.7"}, {rabbitmq_event_exchange,"RabbitMQ Event Exchange","3.9.7"}, {rabbitmq_federation,"RabbitMQ Federation","3.9.7"}, {rabbitmq_random_exchange,"RabbitMQ Random Exchange","3.9.7"}, {rabbitmq_amqp1_0,"AMQP 1.0 support plugin","3.9.7"}, {rabbitmq_management_visualiser,"RabbitMQ Management Visualiser","3.9.7"}, {rabbit,"RabbitMQ","3.9.7"}, {rabbit_common,[],"3.9.7"}, {amqp_client,"RabbitMQ AMQP Client","5.6.0"}, {rabbitmq_prometheus,"RabbitMQ Prometheus Adapter","3.9.7"}]}, {os,{unix,linux}}, {erlang_version,"24.0.5"}, {memory,[{connection_readers,0}, {connection_writers,0}, {connection_channels,0}, {connection_other,13776}, {queue_procs,27512}, {queue_slave_procs,0}, {plugins,0}, {other_proc,106791992}, {mnesia,117776}, {mgmt_db,0}, {msg_index,0}, {other_ets,3172904}, {binary,68324}, {code,27758417}, {atom,235669}, {other_system,12130443}]}, {disk_free_limit,50000000}, {disk_free,192183007232}, {file_descriptors,[{total_limit,1048576}, {total_used,604}, {sockets_limit,943626}, {sockets_used,588}]}, {processes,[{limit,1048576},{used,296}]}, {run_queue,0}, {uptime,576}, {kernel,{net_ticktime,60}}] ``` 在上述输出结果中,我们可以看到 RabbitMQ 的版本号是 “{rabbit,"RabbitMQ","3.9.7"}” . 2. 查看 RabbitMQ 安装目录下的版本文件 在 Linux 中,RabbitMQ 的版本文件位于 /usr/lib/rabbitmq/erlang/lib/rabbitmq_server-x.x.x/ebin/ 目录下,我们可以通过 cd 命令切换到该目录下,执行 ls 命令查看可以看到版本文件名类似 rabbit_common-3.9.7.ez,其中 3.9.7 即为 RabbitMQ 当前的版本。 3. 查看 erlang 包依赖版本 RabbitMQ 是基于 Erlang 编写的,我们可以通过执行以下命令来查看 erlang 依赖的 RabbitMQ 版本: ``` sudo erl -eval '{ok, A} = file:read_file(filename:join([code:root_dir(), "lib", "rabbitmq_server-3.9.7", "ebin", "rabbit.app"])), {ok, [{version, V}]} = re:run(A, ".*{version, \"(\\d+\\.\\d+\\.\\d+)\"}.*", [{capture, [1]}]), io:format("~s~n", [V]), halt().' -noshell ``` 执行完命令后,会输出 RabbitMQ 的版本信息,如下所示: ``` "3.9.7" ``` 以上是 Linux 查看 RabbitMQ 版本的三种方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值