Client-Server Overview

 

现在您已经知道了服务器端编程的目的和潜在的好处,我们将详细研究服务器从浏览器收到"动态请求"时会发生什么. 由于大多数网站服务器端代码以类似的方式处理请求和响应,因此这将帮助您了解编写大多数自己的代码时需要执行的操作.

Prerequisites: 基本的计算机知识. 基本了解Web服务器是什么.
Objective: 要了解动态网站中的客户端-服务器交互,尤其是需要由服务器端代码执行的操作.

讨论中没有真正的代码,因为我们尚未选择用于编写代码的Web框架! 但是,此讨论仍然非常相关,因为所描述的行为必须由服务器端代码实现,而不管您选择哪种编程语言或Web框架.

Web servers and HTTP (a primer)

Web浏览器与通信网络服务器使用H yper T外部贸易交接P rotocol( HTTP ). 当您单击网页上的链接,提交表单或运行搜索时,浏览器会向服务器发送HTTP请求 .

该请求包括:

  • 标识目标服务器和资源的URL(例如HTML文件,服务器上的特定数据点或要运行的工具).
  • 定义所需操作(例如,获取文件或保存或更新某些数据)的方法. 下面列出了不同的方法/动词及其相关的动作:
    • GET :获取特定资源(例如,包含有关产品信息或产品列表的HTML文件).
    • POST :创建新资源(例如,将新文章添加到Wiki,将新联系人添加到数据库).
    • HEAD :获取有关特定资源的元数据信息,而无需像GET那样获取主体. 例如,您可能使用HEAD请求来查找资源的上一次更新时间,然后如果资源已更改,则仅使用(更"昂贵"的) GET请求来下载资源.
    • PUT :更新现有资源(如果不存在,则创建一个新资源).
    • DELETE :删除指定的资源.
    • TRACEOPTIONSCONNECTPATCH :这些动词用于较不常见/高级的任务,因此在此不再赘述.
  • 附加信息可以与请求一起编码(例如,HTML表单数据). 信息可以编码为:
    • URL参数: GET请求通过在发送到服务器的URL的末尾添加名称/值对来编码数据,例如, http://mysite.com ?name=Fred&age=11 . 您总是会有一个问号( ? )将URL的其余部分与URL参数分开,等号( = )将每个名称与其关联的值分开,以及&符( & )将每对分开. URL参数本质上是"不安全的",因为用户可以更改它们然后重新提交. 结果,URL参数/ GET请求不用于更新服务器上数据的请求.
    • POST数据. POST请求添加了新资源,其数据在请求正文中进行了编码.
    • 客户端Cookie. Cookies包含有关客户端的会话数据,包括服务器可用来确定其登录状态以及对资源的权限/访问的密钥.

Web服务器等待客户端请求消息,在到达它们时对其进行处理,然后使用HTTP响应消息答复Web浏览器. 该响应包含一个HTTP响应状态代码,代码指示请求是否成功(例如," 200 OK "表示成功," 404 Not Found "(如果404 Not Found资源)," 403 Forbidden "(如果用户无权查看该请求)资源等). 对GET请求的成功响应的正文将包含所请求的资源.

当返回HTML页面时,它将由Web浏览器呈现. 作为处理的一部分,浏览器可能会发现指向其他资源的链接(例如,HTML页面通常引用JavaScript和CSS页面),并将发送单独的HTTP请求以下载这些文件.

静态和动态网站(在以下各节中讨论)都使用完全相同的通信协议/模式.

GET request/response example

您可以通过单击链接或在网站上搜索(例如搜索引擎首页)来进行简单的GET请求. 例如,当您在MDN上搜索"客户端服务器概述"一词时发送的HTTP请求看起来很像下面显示的文本(由于消息的某些部分取决于您的浏览器/设置,因此它是不相同的).

HTTP消息的格式在"网络标准"( RFC7230 )中定义. 您不需要了解这一详细级别,但是至少现在您知道所有这些信息都来自何处!

The request

请求的每一行都包含有关它的信息. 第一部分称为标头 ,包含有关请求的有用信息,就像HTML头包含有关HTML文档的有用信息(但不是正文中的实际内容本身)一样:

GET https://developer.mozilla.org/en-US/search?q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev HTTP/1.1
Host: developer.mozilla.org
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: https://developer.mozilla.org/en-US/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Charset: utf-8,UTF-8;q=0.7,*;q=0.7
Accept-Language: en-US,en;q=0.8,es;q=0.6
Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _gat=1; _ga=GA1.2.1688886003.1471911953; ffo=true

第一和第二行包含了我们上面讨论的大多数信息:

  • 请求的类型( GET ).
  • 目标资源URL( /en-US/search ).
  • URL参数( q=client+server+overview&topic=apps&topic=html&topic=css&topic=js&topic=api&topic=webdev ).
  • 目标/主机网站(developer.mozilla.org).
  • 第一行的末尾还包含一个短字符串,用于标识特定协议版本( HTTP/1.1 ).

最后一行包含有关客户端Cookie的信息-在这种情况下,您可以看到Cookie包含用于管理会话的ID( Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; ... ).

其余各行包含有关所用浏览器及其可以处理的响应类型的信息. 例如,您可以在这里看到:

  • 我的浏览器( User-Agent )是Mozilla Firefox( Mozilla/5.0 ).
  • 它可以接受gzip压缩信息( Accept-Encoding: gzip ).
  • 它可以接受指定的字符集( Accept-Charset: utf-8,UTF-8;q=0.7,*;q=0.7 )和语言( Accept-Language: de,en;q=0.7,en-us;q=0.3 ).
  • Referer行指示包含此资源链接的网页地址(即请求的来源https://developer.mozilla.org/en-US/ ).

HTTP请求也可以有一个主体,但是在这种情况下为空.

The response

此请求的响应的第一部分如下所示. 标头包含如下信息:

  • 第一行包含响应代码200 OK ,它告诉我们请求成功.
  • We can see that the response is text/html formatted (Content-Type).
  • 我们还可以看到它使用了UTF-8字符集( Content-Type: text/html; charset=utf-8 ).
  • 负责人还告诉我们它有多大( Content-Length: 41823 ).

在消息末尾,我们看到了正文内容,其中包含请求返回的实际HTML.

HTTP/1.1 200 OK
Server: Apache
X-Backend-Server: developer1.webapp.scl3.mozilla.com
Vary: Accept,Cookie, Accept-Encoding
Content-Type: text/html; charset=utf-8
Date: Wed, 07 Sep 2016 00:11:31 GMT
Keep-Alive: timeout=5, max=999
Connection: Keep-Alive
X-Frame-Options: DENY
Allow: GET
X-Cache-Info: caching
Content-Length: 41823



<!DOCTYPE html>
<html lang="en-US" dir="ltr" class="redesign no-js"  data-ffo-opensanslight=false data-ffo-opensans=false >
<head prefix="og: http://ogp.me/ns#">
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  <script>(function(d) { d.className = d.className.replace(/\bno-js/, ''); })(document.documentElement);</script>
  ...

响应标头的其余部分包括有关响应(例如,何时生成响应),服务器以及期望浏览器如何处理页面的信息(例如X-Frame-Options: DENY行告诉浏览器不允许这样做)页嵌入到另一个站点的<iframe>中.

POST request/response example

提交包含要保存在服务器上的信息的表单时,将进行HTTP POST .

The request

下面的文本显示了用户在此站点上提交新的配置文件详细信息时发出的HTTP请求. 该请求的格式几乎与前面显示的GET请求示例相同,尽管第一行将该请求标识为POST .

POST https://developer.mozilla.org/en-US/profiles/hamishwillee/edit HTTP/1.1
Host: developer.mozilla.org
Connection: keep-alive
Content-Length: 432
Pragma: no-cache
Cache-Control: no-cache
Origin: https://developer.mozilla.org
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: https://developer.mozilla.org/en-US/profiles/hamishwillee/edit
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,es;q=0.6
Cookie: sessionid=6ynxs23n521lu21b1t136rhbv7ezngie; _gat=1; csrftoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT; dwf_section_edit=False; dwf_sg_task_completion=False; _ga=GA1.2.1688886003.1471911953; ffo=true

csrfmiddlewaretoken=zIPUJsAZv6pcgCBJSCj1zU6pQZbfMUAT&user-username=hamishwillee&user-fullname=Hamish+Willee&user-title=&user-organization=&user-location=Australia&user-locale=en-US&user-timezone=Australia%2FMelbourne&user-irc_nickname=&user-interests=&user-expertise=&user-twitter_url=&user-stackoverflow_url=&user-linkedin_url=&user-mozillians_url=&user-facebook_url=

主要区别在于URL没有任何参数. 如您所见,表单中的信息被编码在请求的主体中(例如,新用户全名是使用&user-fullname=Hamish+Willee ).

The response

来自请求的响应如下所示. 状态代码" 302 Found "告诉浏览器发布成功,并且它必须发出第二个HTTP请求来加载" Location字段中指定的页面. 否则,该信息类似于对GET请求的响应的信息.

HTTP/1.1 302 FOUND
Server: Apache
X-Backend-Server: developer3.webapp.scl3.mozilla.com
Vary: Cookie
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8
Date: Wed, 07 Sep 2016 00:38:13 GMT
Location: https://developer.mozilla.org/en-US/profiles/hamishwillee
Keep-Alive: timeout=5, max=1000
Connection: Keep-Alive
X-Frame-Options: DENY
X-Cache-Info: not cacheable; request wasn't a GET or HEAD
Content-Length: 0

:在这些例子中的HTTP响应和请求使用捕获的提琴手应用程序,但你可以使用网络嗅探器(如获得类似的信息Websniffer )或浏览器扩展类似的HttpFox . 您可以自己尝试. 使用任何链接的工具,然后浏览站点并编辑配置文件信息以查看不同的请求和响应. 大多数现代浏览器还具有监视网络请求的工具(例如Firefox中的网络监视器工具).

Static sites

静态站点是每当请求特定资源时都会从服务器返回相同的硬编码内容的站点 . 因此,例如,如果您在/static/myproduct1.html有一个有关产品的页面,则将同一页面返回给每个用户. 如果将其他类似产品添加到您的站点,则需要添加另一个页面(例如myproduct2.html ),依此类推. 这可能开始变得效率低下-当您访问数千个产品页面时会发生什么? 您将在每个页面上重复很多代码(基本页面模板,结构等),并且,如果您想更改页面结构的任何内容(例如添加一个新的"相关产品"部分),那么您将必须单独更改每个页面.

注意 :如果页面数量很少,并且您希望将相同的内容发送给每个用户,则静态站点非常有用. 但是,随着页数的增加,它们的维护成本可能很高.

通过再次查看上一篇文章中看到的静态站点架构图,让我们回顾一下它的工作原理.

A simplified diagram of a static web server.

当用户想要导航到页面时,浏览器发送HTTP GET请求,指定其HTML页面的URL. 服务器从其文件系统中检索请求的文档,并返回包含该文档的HTTP响应HTTP响应状态代码 " 200 OK "(表示成功). 服务器可能会返回不同的状态代码,例如,如果文件不存在于服务器上,则显示" 404 Not Found ",如果文件存在但已被重定向到其他位置,则返回" 301 Moved Permanently ".

静态站点的服务器将只需要处理GET请求,因为该服务器不存储任何可修改的数据. 它还不会基于HTTP请求数据(例如URL参数或cookie)更改其响应.

在学习服务器端编程时,了解静态站点的工作原理仍然很有用,因为动态站点以完全相同的方式处理对静态文件(CSS,JavaScript,静态图像等)的请求.

Dynamic sites

动态站点是可以根据特定的请求URL和数据生成和返回内容的站点 (而不是始终为特定的URL返回相同的硬编码文件). 使用产品站点的示例,服务器会将产品"数据"存储在数据库中,而不是单个HTML文件中. 收到产品的HTTP GET请求时,服务器确定产品ID,从数据库中获取数据,然后通过将数据插入HTML模板来构造响应的HTML页面. 与静态站点相比,这具有主要优势:

使用数据库可使产品信息以易于扩展,可修改和可搜索的方式有效地存储.

使用HTML模板使更改HTML结构变得非常容易,因为这仅需要在一个位置,单个模板中完成,而无需在潜在的数千个静态页面中完成.

Anatomy of a dynamic request

本节以上一篇文章中详细介绍的内容为基础,逐步概述了"动态" HTTP请求和响应周期. 为了"保持真实状态",我们将使用运动队经理网站的上下文,教练可以在该网站中以HTML格式选择球队名称和球队规模,并为下一场比赛取回建议的"最佳阵容".

下图显示了"团队教练"网站的主要元素,以及教练访问其"最佳团队"列表时操作顺序的编号标签. 网站中使之动态的部分是Web应用程序 (这是我们将引用处理HTTP请求并返回HTTP响应的服务器端代码的方式), 数据库 ,其中包含有关球员,球队,教练及其教练的信息.关系和HTML模板 .

This is a diagram of a simple web server with step numbers for each of step of the client-server interaction.

教练提交带有球队名称和球员人数的表格后,操作顺序为:

  1. 网络浏览器使用资源的基本URL( /best )向服务器创建HTTP GET请求,并将球队和球员编号编码为URL参数(例如/best?team=my_team_name&show=11)或作为URL的一部分模式(例如/best/my_team_name/11/ ). 之所以使用GET请求,是因为该请求仅获取数据(而不修改数据).
  2. Web服务器检测到该请求是"动态的",并将其转发到Web应用程序进行处理(Web服务器根据其配置中定义的模式匹配规则来确定如何处理不同的URL).
  3. Web应用程序根据URL( /best/ )识别出请求的意图是获得"最佳球队名单",并从URL中找出所需的球队名称和球员人数. 然后, Web应用程序从数据库中获取所需的信息(使用其他"内部"参数来定义哪些球员"最佳",并且还可能从客户端cookie中获取已登录教练的身份).
  4. 通过将数据(来自Database )放入HTML模板内的占位符, Web应用程序可以动态创建HTML页面.
  5. Web应用程序将生成的HTML(通过Web Server )以及HTTP状态代码200("成功")返回到Web浏览器. 如果有任何阻止返回HTML的行为,则Web应用程序将返回另一个代码-例如" 404"以指示该团队不存在.
  6. 然后,Web浏览器将开始处理返回的HTML,发送单独的请求以获取其引用的任何其他CSS或JavaScript文件(请参见步骤7).
  7. Web服务器从文件系统加载静态文件,并将其直接返回到浏览器(同样,正确的文件处理基于配置规则和URL模式匹配).

更新数据库中记录的操作将类似地处理,除了像任何数据库更新一样,来自浏览器的HTTP请求应被编码为POST请求.

Doing other work

Web应用程序的工作是接收HTTP请求并返回HTTP响应. 虽然与数据库进行交互以获取或更新信息是非常常见的任务,但是代码可能同时做其他事情,或者根本不与数据库进行交互.

Web应用程序可能执行的其他任务的一个很好的例子是向用户发送电子邮件,以确认他们在该站点的注册. 该站点可能还会执行日志记录或其他操作.

Returning something other than HTML

服务器端网站代码不必在响应中返回HTML代码段/文件. 相反,它可以动态创建并返回其他类型的文件(文本,PDF,CSV等),甚至返回数据(JSON,XML等).

将数据返回到Web浏览器以便它可以动态更新其自己的内容( AJAX )的想法已经存在了一段时间. 最近,"单页应用程序"变得很流行,其中整个网站都用一个HTML文件编写,该HTML文件在需要时会动态更新. 使用这种类型的应用程序创建的网站将大量计算成本从服务器推向Web浏览器,并且可能导致网站的行为看起来更像本机应用程序(响应速度快等).

Web frameworks simplify server-side web programming

服务器端Web框架使编写代码来处理上述操作更加容易.

他们执行的最重要的操作之一是提供简单的机制,以将不同资源/页面的URL映射到特定的处理函数. 这样可以更轻松地将与每种资源类型关联的代码分开. 它在维护方面也有好处,因为您可以在一个地方更改用于交付特定功能的URL,而不必更改处理程序功能.

例如,考虑下面的Django(Python)代码,该代码将两个URL模式映射到两个视图函数. 第一种模式确保资源URL为/best的HTTP请求将传递到views模块中名为index()的函数. 模式为" /best/junior "的请求将被传递给junior()视图函数.

# file: best/urls.py
#

from django.conf.urls import url

from . import views

urlpatterns = [
    # example: /best/
    url(r'^$', views.index),
    # example: /best/junior/
    url(r'^junior/$', views.junior),
]

注意url()函数中的第一个参数可能看起来有些奇怪(例如r'^junior/$' ),因为它们使用了一种称为"正则表达式"(RegEx或RE)的模式匹配技术. 您不需要知道此时的正则表达式如何工作,除了它们可以使我们匹配URL中的模式(而不是上面的硬编码值)并将它们用作我们的视图函数中的参数外. 例如,一个非常简单的RegEx可能会说"匹配单个大写字母,后跟4至7个小写字母".

The web framework also makes it easy for a view function to fetch information from the database. The structure of our data is defined in models, which are Python classes that define the fields to be stored in the underlying database. If we have a model named Team with a field of "team_type" then we can use a simple query syntax to get back all teams that have a particular type.

下面的示例获取具有准确的(区分大小写)" junior"的team_type的所有团队的列表-请注意格式:字段名称( team_type ),后跟双下划线,然后是要使用的比赛类型(在这种情况下为exact匹配) ). 还有许多其他类型的匹配项,我们可以用菊花链将其链接起来. 我们还可以控制顺序和返回的结果数.

#best/views.py

from django.shortcuts import render

from .models import Team 


def junior(request):
    list_teams = Team.objects.filter(team_type__exact="junior")
    context = {'list': list_teams}
    return render(request, 'best/index.html', context)

junior()函数获取少年团队列表之后,它将调用render()函数,并传递原始的HttpRequest ,HTML模板和定义要包含在模板中的信息的"上下文"对象. render()函数是一种便捷函数,它使用上下文和HTML模板生成HTML,并将其返回到HttpResponse对象中.

显然,Web框架可以帮助您完成许多其他任务. 在下一篇文章中,我们将讨论更多好处以及一些流行的Web框架选择.

Summary

在这一点上,您应该对服务器端代码必须执行的操作有一个很好的了解,并了解服务器端Web框架可以简化此操作的一些方法.

在以下模块中,我们将帮助您为第一个站点选择最佳的Web框架.

In this module