25--- HTTPD(apache)

来源:互联网 发布:怎样知道淘宝店铺网址 编辑:程序博客网 时间:2024/06/05 06:19
===================
协议分层 & socket
在开始 HTTPD 部分之前先简单回顾一下 TCP/IP 协议栈的分层,为什么需要分层呢?是因为要完成网络通信,需要分为好几步,就好比要把大象装冰箱要分几步,每一层其实就是一些 rutine,完成通信中的一步或几步动作,上层 ruitine 调用下层的 rutine 完成工作,这也就是为什么大家常说下层为上层服务。再举一个例子。比如要做馒头,就要种麦子,收麦子,磨面粉,和面,发酵,蒸馒头。怎么能够一步到位呢?蒸馒头尚且如此,通信系统中的两点间通信更是如此。只不过有些动作我们不需要自己做,而是操作系统帮我们做好了,就像我们只要调用 socket 说我需要面粉,socket 就会安排人去种麦子,收麦子,磨面粉,而我们只要负责和面,发酵和蒸馒头就好。再次强调,所谓分层,就是完成一件事要分几步,一个层完成其中的一步或几步,仅此而已。(补充一句,计算机系统中常常会把大家都用到的功能剥离出来形成一层或几层,然后留出接口供其他功能调用;另一个常见到的层的概念是用于屏蔽底层的不同,比如 VFS 这一层就屏蔽了各个文件系统的差异,并向上层提供接口。)
那么什么是 socket 呢?在讲 socket 之前我们先想一个问题。

操作系统的作用是什么?是把硬件资源抽象给使用者使用,这个使用者可以理解为普通用户和程序员。怎么理解把资源抽象然后提供给用户使用呢?举个例子,当我们用 cp 这个命令的时候,操作系统时怎么复制文件的?实现过程简单来讲是这样,先调用一个 read() 的 systemcall(也就是系统调用),把数据读出来,然后调用另一个名为 write() 的 systemcall,把读出来的数据写入磁盘,这样就完成了复制动作。 可以把 read() 和 write() 这两个系统调用理解为操作系统提供给我们的资源或资源操作接口。当程序员编写程序供普通用户的时候,如果没有操作系统,程序员只能从最基本的驱动写起,自己动手与硬件打交道,这显然是麻烦的。操作系统就是把大家都用到的操作,比如磁盘I/O,比如网络操作等写到内核里面,并留出接口(系统调用)给程序员使用(通常以函数的形式给出),然后程序员调用此接口告知内核应该做什么。这样一来,只要操作系统内核来完成某些动作就好了,应用开发的程序员就不需要开发此类底层程序了,想想世界上有成千上万的程序员,操作系统的这种模块共享功能节约了多少时间啊。再次借用上面的例子,操作系统负责种麦子,收麦子,磨面粉,这些活儿它全承包了,然后把面粉卖给做面条的,做烧饼的,做面包的。此外还有些人在面粉中加入了颜色,然后拿来卖给手工艺人捏面人(当然也可以卖给做面条的,做包子的等),这种掺了颜色的面粉我们可以理解为库,是对 systemcall 的一种封装,通常来讲功能上会更加复杂,比如 printf() 就封装了 puts() 的调用,加了一些格式输出的功能。
其实网络操作也是如此,先讲一下网卡接收到数据时候的处理。当网卡接收到数据,一般网卡都会把数据放在自己的寄存器中,显然这个寄存器的大小是有限的,一般来讲,这时候就会有中断产生告知主程序(也就是操作系统)数据到了,请来取数据。然后操作系统(准确说是 rutine 中的驱动模块)从网卡中读数据到内存中,然后交给二层协议栈 rutine (当然也是内核的一部分)处理,二层 rutine 做完各项检查根据二层中标记的三层协议类型让相应的三层 rutine 来处理信息,三层以同样的方式检查并交给四层例程处理之。四层的例程(常见的UDP,TCP,SCTP等)检查端口号,然后对应的进程处理。而四层的 rutine 在内核空间,其代码是以特权级别运行的,而应用程序,如监听80的HTTPD,运行在用户空间。那两者怎么通信呢?这就要用到 socket。常用的 socket API 有如下几个(此处没有给全,详情见“Linux C编程”系列中socket的部分),简单了解下。
-----
int socket(int domain, int type, int protocol);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
作为客户端,socket() + connect() = 创建 socket 并将其连接到远端 server
作为服务端,socket() + bind() + listen() = 创建 socket 并指定IP及端口,同时启动监听,等待来自于客户端的连接请求
-----
网卡接收数据是这样处理的,那如果需要往外发数据呢?也是如此,先调用 socket() 生成一个 socket,然后调用 connect() 把对端的(IP:Port)写到 socket,其实就是告诉内核程序中的 TCP/IP 协议栈,我要和该地址通信,你们准备好。等内核准备好,发送端就可以调用发送数据的系统调用发送数据了。上面讲到的接收数据过程也需要建立 socket,由于本机可能会有多个IP,这就需要通过 bind() 来指定(IP:Port),所以我们认为一个(IP:Port)对儿就确定了一个 socket,然后就可以调用 listen() 开始监听这个(IP:Port)对儿。
至此我们应该理解,socket API 其实就是和内核的网络部分打交道的一个接口。创建 socket 就是在内存中创建一个数据结构,该结构可以认为是本端通信的端点。
=================
HTTPD(apache)
-----CentOS 6.0 上 httpd-2.2.15-----
HTTPD 是 core + modules 的软件,很多功能是以 DSO(Dynamic Shared Objects)的形式给出。可以通过 httpd -M(或httpd.worker -M / httpd.event -M)查看你有哪些 DSO。
配置文件
/etc/httpd/conf/httpd.conf
/etc/httpd/conf.d/*.conf          <--- 在httpd.conf主配置文件中有 Include conf.d/*.conf
修改完配置文件后,使用service httpd configtest 或 httpd -t 检查下语法
服务脚本:
/etc/rc.d/init.d/httpd
    脚本的配置文件 /etc/sysconfig/httpd
主程序文件:
/usr/sbin/httpd
/usr/sbin/httpd.event
/usr/sbin/httpd.worker
日志文件目录:
/var/log/httpd
    access_log  访问日志
    error_log   错误日志
-----
主配置文件 /etc/httpd/conf/httpd.conf 主要分为如下 3 个部分: 
### Section 1: Global Environment
### Section 2: 'Main' server configuration  <--- 
### Section 3: Virtual Hosts
修改配置文件后需要 reload 或 restart 服务以生效。
配置文件的格式:
directive value
常用 directive(指令)说明:
-----Global Environment-----
ServerRoot "/etc/httpd"    <--- 日志等服务器信息的存储相对于此目录(其实log,module,run等目录都是软链接)
-----
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 15
说明:一个 page 上的资源往往不止一个,每个资源都需要发送 request 给服务器,KeepAlive 如果为 off 则每请求一个资源都需要建立/拆除 TCP 连接,如果页面资源较多(如多图)就会导致不停地建立和拆除TCP连接(每次返回一个response后,服务器拆连接),访问资源效率低下。这种连接被称之为短连接。我们一般讲 KeepAlive 设置为 on,以建立长连接,但是长连接有其副作用,即如果 TCP 连接建立后没有和服务器的交互(没有 request/response),那么就会占用服务器资源(一直有个进程在等待服务请求),这样也不好,尤其是并发访问较多时。所以这里通过KeepAliveTimeout 来控制两个 request 间的最大时长,如果第N个request后一直没有下一个 request,则 15 秒后断开 TCP 连接。此外MaxKeepAliveRequests 设置了每个连接的最大 request 数,建议这个值调的大一点。其实设置 MaxKeepAliveRequests 可以认为是对其他用户的一种公平,尤其是并发请求量比较大的时候。
可以通过 Telnet 到 httpd 的端口上,以手动发送 HTTP 报文的方式来验证长连接和短连接。
telnet 192.168.10.131 80
GET /index.html HTTP/1.1
Host: 192.168.10.131   <--- 这个 Host 首部的值会被 HTTPD 得到后选择 VirtualHost,下面会讲到 VirtualHost
两次回车; 如果是短连接的话可以看到服务器发送了 Connection 首部,其值为 close,即 Connection : close,并关闭了 TCP 连接。如果为长连接,则看到服务器没有关闭连接,我们可以继续在命令行中发送信息给服务器。
-----
# prefork MPM
<IfModule prefork.c>
StartServers       8
MinSpareServers    5
MaxSpareServers   20
ServerLimit      256
MaxClients       256
MaxRequestsPerChild  4000
</IfModule>
# worker MPM
<IfModule worker.c>
StartServers         4
MaxClients         300
MinSpareThreads     25
MaxSpareThreads     75
ThreadsPerChild     25
MaxRequestsPerChild  0
</IfModule>
HTTPD 的 MPM(Multi Processing Modules) 支持prefork(多进程处理并发),worker(多进程&多线程处理并发),event(基于事件机制处理并发),尽管我们说 HTTPD 是 core + DSO 的组织形式,但是 2.2 版本中这些功能是被单独编译进 HTTPD 的,而非以 .so 的动态库来提供。rpm 包为我们提供了编译好的3个程序,分别是 httpd,httpd.worker 和 httpd.event ,我们可以通过 httpd -l,httpd.worker -l 和 httpd.event -l 来查看编译时候使用的 c 源文件(然后对比 httpd -M , httpd.worker -M , httpd.event -M)。遗憾的是在 2.2 版本中 event 机制还在测试阶段,到 2.4 版本才打到用于生产的稳定性。 
可以在 /etc/sysconfig/httpd 中修改 HTTPD= 的值来更换MPM,但是生产中 HTTPD 常见的 MPM 还是 prefork 形式。
-----
Listen [IP:]#    // 监听的 IP 和 Port,IP 省略表示监听本机所有 IP
LoadModule 模块名 模块路径    // 控制载入哪些模块
-----Main Server-----      <--- 此 section 的部分 directive 也作为 Virtual Hosts 的默认值,即若使能 Virtual Hosts 但有没有给某directive 赋值,则默认用本section的值。
ServerName www.freeland.com:80    <--- 服务器的FQDN
DocumentRoot "/var/www/html"    <--- 服务器提供资源的根位置
Alias /manual/ "/usr/share/httpd/manual/"   <--- 目录别名,此后访问http:/hostname/manual 就是访问服务器上/usr/share/httpd/manual 这个目录(文件系统路径哦)
DirectoryIndex index.html index.txt   <---若请求某目录,则默认页面由此设置,index.html 位于被请求的目录下
ErrorLog logs/error_log    <--- 错误日志,相对位置是相对于 Global Environment 中的 ServerRoot,但要知道 $ServerRoot/log 目录是软链接,真正的日志在 /var/httpd/log 目录下
LogLevel { warn | debug | info | notice | error | crit | alert | emerg }    <--- ErrorLog 的等级,默认 warn
CustomLog logs/access_log combined    <--- Custom日志,格式为 CustomLog PATH { common | referer | agent | combined }
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined    <--- 日志格式
LogFormat "%h %l %u %t \"%r\" %>s %b" common
%h    <--- 远程主机的IP(HostnameLookups=off 时,这个HostnameLookups 默认就是off)
%l    <--- 较少使用,常见值为 -
%u    <--- HTTP协议验证时键入的用户名(下文会讲解定义安全域)
%t    <--- 收到 request 的时间
%r    <--- request 的第一行
%>s    <--- 最终状态码  %s为其实状态码
%b    <--- response 的字节数
%{Referer}i    <--- 获取 request 头部中 Referer 的值,该值标示从哪里跳转到该页面
%{User-agent}i    <--- 获取用户agent,也就是浏览器的类型
-----访问控制
<Directory "/var/www/my_example">    <--- 基于文件系统绝对路径的访问控制,即访问 "/var/www/my_example" 时按如下 directive 处理
    Options [Indexes] [FollowSymLinks] [MultiViews]    <--- Indexes 表示GET 该目录时是否返回目录下文件信息(处于安全方面考虑,不建议使用);FollowSymLinks 表示 GET 的资源为软链接时,是否允许 response 被链接文件。
    AllowOverride None
    Order allow,deny
    Allow from all    <--- 常常是对 IP 的限制,如 Allow from 172.16 就是对 172.16.x.x 的访问允许
</Directory>
-----
<Location /hehe>    <--- 基于URL路径的访问控制,即访问 http://host/hehe 时按如下 directive 处理
    SetHandler server-status    <--- 调用 server-status
    Order deny,allow
    Deny from all
    Allow from 192.168.10.1
</Location>
-----定义安全域(此处以Directory为例,亦可用 Location对某URL做控制)
Step-01 修改配置文件,将指定目录设置为访问控制
<Directory "/var/www/html/test/admin">    <--- 注意这里的目录是绝对路径,是文件系统上的绝对路径!!!
        Options None
        AllowOverride None
        AuthType Basic
        AuthName "Please give password."
        AuthUserFile "/etc/httpd/conf.d/.httpdusers"
        AuthGroupFile "/etc/httpd/conf.d/.httpdgrps"  <-- 手动创建,用户应该已经创建。 内容为 webgrp:tom jack
        Require valid-user      // 或 Require user tom ,其中 tom 必须已经在AuthUserFile中创建
  #    Require group webgrp  
</Directory>
Step-02  生成一个文本文件存储账号
    htpasswd [option] PASSWDFILE username
    -c  自动创建passwdfile,第一次添加用户时候使用,添加用户到某文件  <-- 即上面的AuthUserFile 
    -m  md5加密用户密码
    -s   sha1加密用户密码
    -D  删除用户
    htpasswd -c -m /etc/httpd/conf.d/.httpdusers tom
    htpasswd -m /etc/httpd/conf.d/.httpdusers jack
当访问某页面时候需要键入用户名密码方可继续访问。http://tom:tom@192.168.10.131/admin/admin.html 可以直接把用户名和密码放入URL传给server
-----
Alias
-----Virtual Hosts-----
虚拟主机,一个主机定义多个站点 。3 种实现方案:基于ip,基于port,基于hostname ;简单描述如下:
基于IP:为每个虚拟主机准备至少一个IP地址  <-- 少用
基于Port: 为每个虚拟主机单独分配port  <-- 少用
基于Hostname:为每个虚拟主机准备至少一个专用的主机名(FQDN)
Note: 一般虚拟主机不与中心主机(Main Server)混用,启用虚拟主机后,当资源请求到达时,httpd 会先去虚拟主机匹配。
-----基于IP
<VirtualHost IP1:80>
    ServerName web1.free.com
    DocumentRoot /www/docs1
    ErrorLog logs/web1-error_log
    CustomLog logs/web1-access_log common
</VirtualHost>

<VirtualHost IP2:80>
    ServerName web2.free.com
    DocumentRoot /www/docs2
    ErrorLog logs/web2-error_log
    CustomLog logs/web2-access_log common
</VirtualHost>
-----基于Port
<VirtualHost IP1:80>
    ServerName web1.free.com
    DocumentRoot /www/docs1
    ErrorLog logs/web1-error_log
    CustomLog logs/web1-access_log common
</VirtualHost>

<VirtualHost IP1:8080>
    ServerName web3.free.com
    DocumentRoot /www/docs3
    ServerAdmin 
   ErrorLog logs/web3-error_log
    CustomLog logs/web3-access_log common
    ServerAlias
</VirtualHost>
-----基于Hostname
NameVirtualHost IP:80    <--- HTTP 2.2 上若要使用基于FQDN的虚拟主机, NameVirtuslHost 这个 directive 一定要启用(默认别注释)
<VirtualHost IP1:80>
    ServerName web111.free.com
    DocumentRoot /www/docs1
    ErrorLog logs/web111-error_log
    CustomLog logs/web111-access_log common
</VirtualHost>

<VirtualHost IP1:80>
    ServerName web222.free.com
    DocumentRoot /www/docs3
    ErrorLog logs/web222-error_log
    CustomLog logs/web222-access_log common
</VirtualHost>

<VirtualHost IP1:80>
    ServerName web333.free.com
    DocumentRoot /www/docs3
     ErrorLog logs/web333-error_log
     CustomLog logs/web333-access_log common
</VirtualHost>
浏览器中需要键入ServerName 就可以分别访问
============= 内置的status 页面  通过http://ip/server-status访问
<Location /server-status>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from .example.com
</Location>
-----HTTPS的配置
实现 HTTPS 就是在 VirtualHost 下配置一些 HTTPS 相关的 directive ,如:使能 ssl 引擎等。  
httpd -M | grep ssl 检查 ssl_module 模块是否安装 ,若未安装则通过 yum 安装之,yum -y install mod_ssl
rpm -ql mod_ssl    <-- 主要关注 /etc/httpd/conf.d/ssl.conf 和 /usr/lib64/httpd/modules/mod_ssl.so
先分析下 /etc/httpd/conf.d/ssl.conf    <--- conf.d下*.conf 都被 /etc/httpd/conf/httpd.conf 所包含(通过 include)
LoadModule ssl_module modules/mod_ssl.so    // 加载 ssl 模块
Listen 10086    // 设定监听端口
<VirtualHost 192.168.10.131:10086>    <--- 该虚拟机绑到(IP:Port)对儿上
DocumentRoot "/var/www/https"
ServerName www.freeland.com    <--- 指定 FQDN
ErrorLog logs/ssl_error_log
TransferLog logs/ssl_access_log
LogLevel warn
SSLEngine on    // 对该 VirtualHost 使能 ssl 引擎
SSLProtocol all -SSLv2    // 减号代表disable
SSLCipherSuite DEFAULT:!EXP:!SSLv2:!DES:!IDEA:!SEED:+3DES
SSLCertificateFile /root/www.freeland.com.crt    // 服务器公钥证书(文件系统上的绝对路径)
SSLCertificateKeyFile /root/myhttp.key    // 服务器私钥(文件系统上的绝对路径)
</VirtualHost>
///// 公钥证书可以通过 openssl 生成,参考博文《22---加密解密原理及openssl》,注意要关闭 SELinux ,否则会提示我们的公钥证书有问题。/////
注意:以上操作后若启动 httpd 失败,请查阅日志 /var/log/httpd下 的 error_log 日志。 有一次我把 ErrorLoglogs/web1_error_log 错写为 ErrorLog log/web1_error_log ,结果启动失败,又被 Starting httpd: httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName所误导,所以排错了一个多小时才搞定,惭愧啊!
-----CentOS 7.x 上 httpd-2.4.6-----
主要说下 2.4 和 2.2 的几个不同:
(1). 配置文件
/etc/httpd/conf/htpd.conf
/etc/httpd/conf.d/*.conf  
/etc/httpd/conf.modules.d/*.conf <--- 多了模块相关的配置目录,模块相关的配置放在该目录
(2). prefork, worker, event 不再被静态编译进core中,而是以 module 的形式提供,选用哪一个 module 可以在 conf.modules.d 下的 00-mpm.conf 中指定。
(3). KeepAlive & Timeout 可以做到毫秒级别
(4). 对资源的访问更加严格(不再使用 Order,Allow,Deny)-----控制特定IP访问
Require ip IPADDRESS;  
Require not ip IPADDRESS;  <---其中IPADDRESS可以为IP,Network/Mask,Network/Length,Net(如172.16)
Require all granted
Require all deny
注意:httpd2.4 对于某路径的访问需要显式授权(子路径除外,如果父路径被允许访问,则子路径默认继承之,即亦可被访问)
<Directory "xxx">  <--- DocumentRoot未被显式授权则禁止访问
    Options None
    AllowOverride None
    Require all granted
</Directory>
也就是分两种情况:
1.如果某要控制的Directory是一个(虚拟)主机的DocumentRoot,则默认是all denied,需要显式授权才能访问
2.如果某要控制的Directory是一个DocumentRoot下子目录(或子子目录,子子子目录。。。),则默认被访问权限继承父目录
<RequireAny>    // 这个标签和下面的<RequireAll>要留意
        Require ip 192.168.10.1     
        Require all denied
</RequireAny>

<RequireAll>
        Require not ip 192.168.10.1     
        Require all granted
</RequireAll>
(5). 对于虚拟主机的设置,不再需要 NameVirtualHost [IP:]PORT











原创粉丝点击