从零开始搭建一个简易的服务器(二)


超级大坑

第一篇博客到现在拖坑有半年了(不过估计也没人记得我),原本的打算是既然要写服务器,那自然要设计一门语言,类似于php这样的工作于服务器后端负责后端渲染,然后到目前为止的时间基本都花在写编译器上了囧,编译器的项目在这里。如果真的等编译器全部写完估计我大学也毕业了,为了避免继续拖坑下去,只能在半成品都拿不出手的情况下继续更新博客。关于python的简易服务器,半年前写了一个demo版,顺带搭载了一个类似于球球大作战的游戏(游戏逻辑不全,如果要完整实现的话,至少得拿出一整周的时间,学业为重)。

三、分流请求

在实际的代码生产的过程中,我们会自然的诞生这样一个需求,那就是根据用户请求的内容,将请求分流到不同的主机上进行处理,然后将处理结果返回给客户机。当然,在这样一个微型的服务器框架中,自然是不用考虑多主机的,一切以能运行为前提。为了模拟多主机,我们可以为每一个请求创建一个线程,然后由线程搜集资源并将资源返回给客户机(python的线程虽然是用户级的伪多线程,但是在这样一个微型框架中已经足够了),有了这些信息,我们可以将代码大略写成这样:

import thread, socket
def handler(path):
  handle_request()
...
s = socket(AF_INET, SOCK_STREAM)
...
while 1:
  conn, addr = s.accept()
  thread.start_new_thread(handler, (path))

上述代码仅仅是伪代码层次的python,因为我们并不知道handle_request究竟干了什么以及是如何干的。这里我们便可以回顾标题:分流请求,所谓分流请求,狭隘的理解的话,可以看成根据请求的资源不同调用不同的函数并将函数的返回值(具体的网页源码或一些其他资源)返回给用户端。也就是说handle_request大概有这样的一种形式:

def handle_request(path):
  if path == 'a.png':
    return get_content_of_file('a.png')
  elif path == 'b.css':
    return get_content_if_file('b.css')
  ...

多层嵌套的if-else无疑难以忍受的,所以一般而言,我们都会尽可能用其他的方法代替多层if-else,比如常见的打表法,类似于C语言中的switch-case,只不过由于python中有字典这一强大的数据结构,python的打表远可以完成更多C语言switch-case所无法完成的功能。我们可以将用户请求的资源路径作为键,传入字典获取得到对应的值(处理函数),然后调用这个函数处理用户请求,由此我们便有了这样一种形式的handle_request

def normal_resource(path):
  with open(path) as fp:
    return fp.read()

def ack_404(path):
  return 'resource {} not found'.format(path)
...
handler_list = [
  ('a.css', normal_resource),
  ('b.txt', normal_resource),
  ...
]
...
def handle_request(path):
  if handler_list.has_key(path):
    handler_list[path](path)
  else:
    ack_404(path)

四、利用正则表达式更宽泛的分流请求

假设现在老板给我们这样一个请求,对所有hide目录下的文件都返回404,且不谈老板脑袋是不是抽了,在拿到这样一个请求时,之前的严格键值匹配的方式也就不在适用了,因为我们总不能对hide目录下的每一个文件都记录一个表项(即便这样处理了,我们也没法应对随时会增加文件的目录,而这样的目录在web中很常见)。

因此,我们必须要另寻它法,一个显而易见的想法就是利用正则表达式进行模式匹配,一旦匹配到某个模式就调用对应的处理函数进行处理,将数据返回给用户。由此,我们可以将之前的框架进一步升级:

import re
...
handler_list = [
  (r'^hide/.*$', ack_404),
  (r'^b.txt$', normal_reource),
  (r'^.*$', ack_404), # 所有前面的模式都匹配失败请求交由404处理
  ...
]
...
def handle_request(path):
  for regex, handler in handler_list:
    if re.match(regex, path):
      handler(path)

这样一来,老板无论要隐藏多少个文件夹,我们都不用再愁了,因为我们已经掌握了正则表达式这样一个大杀器(正则表达式虽然是4型文法,但其描述能力对于我们的日常使用来说已经是足够的了)。

五、剔除path中不需要的信息。

在django这样相当成熟的后端框架中,我们可以看到这样一个非常有用的功能,那就是正则表达式的分组匹配到的内容可以直接作为参数传给对应的函数,而对应的处理函数也只要确保函数参数个数一致就可以剔除掉不需要的那些信息。

为了举一个相对应的例子,我们可以假设老板脑袋又抽风了,他想让自己的网站可以做一些简单的加减操作,比如用户请求网页/add/a/b时,网站返回给用户计算好的值a+b,比如add/1/2返回3。

在第节中的框架里,想要完成这个功能我们必须手动解析path,匹配出数字a和b对应的串,再将计算结果返回,但是我们为什么不能提前就把必要的信息a和b匹配出来,然后交给对应的handler函数呢?有了这样的想法,我们自然要绞尽脑汁去实践它。

对于这样的需求,我们会自然而然的想到不定参函数或许可以做到这一点,然后解决问题的动力便会促使我们回头再翻一遍廖雪峰大大的python教程,尤其是讲变参函数的那一章节,仔细翻阅之下,我们终于翻到了一个切实有用的特性可以辅助我们达成上面的需求,该特性描述如下:
对于函数:

def add_two_numbers(a, b)
  return a+b

我们除了正常的如add_two_numbers(1, 2)的调用方式,还可以有:

nums = [1, 2]
add_two_numbers(*nums)

这样的调用方式。

符号*可以将list一层展开,然后将每个元素作为一个独立的参数传给函数,元素的个数同时也是函数实际收到的参数个数。

至此我们便可以将上一节的代码再次进行升级:

def add_two_numbers(a, b):
  return str(int(a)+int(b))
...
handler_list = [
  (r'^(hide/.*)$', ack_404),
  (r'^(b.txt)$', normal_reource),
  (r'add/(\d+)/(\d+)', add_two_numbers),
  (r'^(.*)$', ack_404),
  ...
]

def handle_request(conn, addr, path):
  for regex, handler in handler_list:
    match_result = re.match(regex, path)
    if match_result:
      handler(*match_result.groups())

结语

这个微型框架的全部代码可以在这里获得,不过说实话,这个框架能否称为框架都还是个问题,因为实在是太过简陋了,甚至都没有会话的功能。不过博主作为万年C程序员,学python也只是心血来潮,如果以后真的要开发后端框架,也只会是用C而不是python(不过或许可以改造一下C,使得其写起来不那么蛋疼)。

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值