写在最后
在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。
需要完整版PDF学习资源私我
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
class Application {
callback = (req, res) => {
res.end(‘Hello World\n’);
}
listen() {
const server = http.createServer(this.callback);
console.log(…arguments)
server.listen(…arguments)
}
}
**测试一下:test.js**
const Koa=require(‘./application.js’)
const app=new Koa()
app.listen(3000)
可以正常访问
![image.png](https://img-blog.csdnimg.cn/img_convert/da16cf0bb50569855f2ac0bb782abdea.png)
#### app.use
在前置知识中我们看到,app.use接收一个回调函数,同时传入一个ctx上下文,这里ctx将request和response封装起来。为了清晰易懂,我们先不进行上下文的封装。
app.use(async ctx => {
ctx.body = ‘Hello World’;
});
那么use简单的处理如下:
class Application {
use(fn) {
this.fn=fn
}
}
此时use接收了一个函数,这个函数的执行的时机是在访问网站的时候,那么此时就需要在创建http服务的时候,传入这个函数。最好的方式就是放在listen的callbak中调用。
callback = (req, res) => {
this.fn(req, res)
}
#### 最终代码
let http = require(‘http’)
class Application {
use(fn) {
this.fn=fn
}
callback = (req, res) => {
this.fn(req, res)
}
listen() {
const server = http.createServer(this.callback);
server.listen(...arguments)
}
}
module.exports = Application
**测试:test.js**
const Koa=require(‘./application.js’)
const app=new Koa()
app.use((req, res) => {
res.end(‘Hello World\n’);
})
app.listen(3000)
可以正常访问
![image.png](https://img-blog.csdnimg.cn/img_convert/da16cf0bb50569855f2ac0bb782abdea.png)
### 封装ctx
明确一个事实:**每个请求都是独立的**,对于原生的http请求来说,每次请求的**response和request是不同的**。对于koa中的ctx,则表示**每次请求的ctx都是一个新的ctx**。
#### ctx的结构
为了看到ctx的结构,我们先使用源koa打印一下ctx。最终得到的结果如下所示,有了这个结构我们就可以实现一个自己的ctx。
下面这个格式是console.dir(ctx)的结果(删掉了一些具体的内容),从下面的内容,我们可以得出ctx的结构。。
{
request: {
app: Application {
},
req: IncomingMessage {
},
res: ServerResponse {
},
ctx: [Circular],
response: {
app: [Application],
req: [IncomingMessage],
res: [ServerResponse],
ctx: [Circular],
request: [Circular]
},
originalUrl: '/'
},
response: {
app: Application {
},
req: IncomingMessage {
},
res: ServerResponse {
},
ctx: [Circular],
request: {
app: [Application],
req: [IncomingMessage],
res: [ServerResponse],
ctx: [Circular],
response: [Circular],
originalUrl: '/'
}
},
app: Application {
},
req: IncomingMessage {
},
res: ServerResponse {
},
originalUrl: ‘/’,
state: {}
}
#### context.js
context.js 主要定义了context的具体结构以及提供的方法。
Koa Context 将 node 的 `request` 和 `response` 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。 这些操作在 HTTP 服务器开发中频繁使用,它们被添加到此级别而不是更高级别的框架,这将强制中间件重新实现此通用功能。
#### request.js和response.js文件
在核心目录,我们提到了这两个文件,这两个文件此时就派上了用场。这两个文件具体实现了啥呢?**这两个文件定义了ctx.resopnse和ctx.request的结构**,也就是上面使用dir输出的结果。在[koa中文文档](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)中可以具体的看到结构,可以自行查阅。
Koa `Request` 对象是在 node 的 原生请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。
Koa `Response` 对象是在 node 的原生响应对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。
#### 实现ctx
##### 定义context.js
const context={
}
module.exports=context
##### 定义request.js
const request={
}
module.exports=request
##### 定义response.js
const resposne={
}
module.exports=response
##### use中封装ctx
我们在上面导出koa章节中可以看到,在app.use的时候,我们传的参数是(request,response),源koa传的的ctx,所以我们就知道了,koa是在app.use的时候创建了一个ctx。
在本章开头的时候,我们又提到每次请求的ctx都是全新的ctx。
综合以上两点,我们可以基本编写出下面的代码。(**为了代码的清晰易读,我们封装了一个createcontext函数来创建上下文。**)
const Context = require(‘./context’)
const Request = require(‘./request’)
const Response = require(‘./response’)
class Application {
constructor(){
this.context = Object.create(Context);
this.request = Object.create(Request);
this.response = Object.create(Response);
}
use(fn) {
this.fn = fn
}
createContext = (req, res) => {
const ctx = Object.create(this.context);
const request = Object.create(this.request);
const response = Object.create(this.response);
ctx.app = request.app = response.app = this
ctx.request = request;
ctx.request.req = ctx.req = req;
ctx.response = response;
ctx.response.res = ctx.res = res;
ctx.originalUrl = request.originalUrl = req.url
ctx.state = {}
return ctx
}
callback = (req, res) => {
let ctx = this.createContext(req, res)
this.fn(ctx)
}
listen() {
const server = http.createServer(this.callback);
console.log(...arguments)
server.listen(...arguments)
}
}
首先我们在constructor中定义了一个context对象,这里会在constructor定义是因为Koa的app上默认导出context属性。
>
> `app.context` 是从其创建 `ctx` 的原型。您可以通过编辑 `app.context` 为 `ctx` 添加其他属性。这对于将 `ctx` 添加到整个应用程序中使用的属性或方法非常有用,这可能会更加有效(不需要中间件)和/或 更简单(更少的 `require()`),而更多地依赖于`ctx`,这可以被认为是一种反模式。
> 例如,要从 `ctx` 添加对数据库的引用:
>
>
>
> ```
> app.context.db = db();
> app.use(async ctx => {
> console.log(ctx.db);
> });
>
> ```
>
>
然后再callback中,我们针对response和request进行了二次封装。
再来看看这段代码:
app.context.db = db();
app.use(async ctx => {
console.log(ctx.db);
});
再使用use之前,通过app.context对context进行了修改。当使用use函数的时候,是不是直接进入了callback函数,此时的this.context已经是修改过的了。
##### 测试
const Koa=require(‘./application.js’)
const app=new Koa()
app.use((ctx) => {
// 测试1
ctx.response.res.end(" hello my koa")
// 测试2
ctx.res.end(" hello my koa")
})
app.listen(3000,()=>{
console.log(‘3000’)
})
正常访问!
![image.png](https://img-blog.csdnimg.cn/img_convert/f709faa8ad4d3a4e2b56331b34107886.png)
### 封装request.js
明确一个事实:**request类的属性是通过getter和setter设置的**。为什么会这样设置?这样设置的好处是可以方便的设置和获取到值。是不是有点懵逼!请听我细细道来。
先来看一下Koa中request类所绑定的属性,[官方链接](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)。
我这里简单的列举几个:
request.header=
设置请求头对象。
request.headers
请求头对象。别名为 request.header
.
request.headers=
设置请求头对象。别名为 request.header=
request.url
获取请求 URL.
request.url=
设置请求 URL, 对 url 重写有用。
request.originalUrl
获取请求原始URL。
1. 这里对于**每个属性都有设置和获取的功能**,使用getter和setter可以很好的实现。
2. 这里的每个属性是如何**获取**的呢?还记得我们在request绑定了一个啥?node http原生的request(req)对不对,当我们使用Object.create并ctx.request.req=req之后,对于当前的request对象是不是都有了一个req属性。那么是不是可以通过getter进行获取。
get url () {
return this.req.url
},
3. 这里的每个属性是如何**设置**的,如果我们对request本身设置有效吗?
例如下面的结构:
const request={
url:‘’,
header:{
}
}
我们直接request.url=“https://juejin.cn/”, 这样会导致一个bug,是啥呢?还记得 我们的数据是从哪里取得的,req中获取的,如果你set的时候不是对req.url进行设置,你能获取到这个值吗?所以request的结构是这样子的。
const request={
set url (val) {
this.req.url = val
}
get url () {
return this.req.url
},
}
#### request.socket的getter
socket在这里指套接字。套接字的概念这里不赘述!
get socket () {
return this.req.socket
},
#### request.protocol的getter
返回请求协议,“https” 或 “http”。当 `app.proxy` 是 **true** 时支持 `X-Forwarded-Proto`。
先判断套接字中是否存在encrypted(加密),如果加密,就是https,
X-Forwarded-Proto用来确定客户端与代理服务器或者负载均衡服务器之间的连接所采用的传输协议(HTTP 或 HTTPS)
X-Forwarded-Proto: https
X-Forwarded-Proto: http
get protocol () {
if (this.socket.encrypted) return ‘https’
if (!this.app.proxy) return ‘http’
const proto = this.get(‘X-Forwarded-Proto’)
return proto ? proto.split(/\s*,\s*/, 1)[0] : ‘http’
},
这里有一个get函数,主要时根据字段,从请求头中获取数据。
get (field) {
const req = this.req
switch (field = field.toLowerCase()) {
case ‘referer’:
case ‘referrer’:
return req.headers.referrer || req.headers.referer || ‘’
default:
return req.headers[field] || ‘’
}
},
#### request.host的getter
存在时获取主机(hostname:port)。当 `app.proxy` 是 **true** 时支持 `X-Forwarded-Host`,否则使用 `Host`。
get host () {
const proxy = this.app.proxy
let host = proxy && this.get(‘X-Forwarded-Host’)
if (!host) {
if (this.req.httpVersionMajor >= 2) host = this.get(‘:authority’)
if (!host) host = this.get(‘Host’)
}
if (!host) return ‘’
return host.split(/\s*,\s*/, 1)[0]
},
#### request.origin的getter
获取URL的来源,包括 `protocol` 和 `host`。
例如我请求:http://localhost:3000/index?a=3,
origin返回的是http://localhost:3000
get origin () {
return ${this.protocol}://${this.host}
},
#### request.href的getter
获取完整的请求URL,包括 `protocol`,`host` 和 `url`。
href支持解析 `GET http://example.com/foo`
例如我访问http://localhost:3000/index?a=3
href返回http://localhost:3000/index?a=3
get href () {
if (/^https?:\/\//i.test(this.originalUrl)) return this.originalUrl
return this.origin + this.originalUrl
},
**注意:这里的this.originalUrl在封装ctx的时候已经绑定过了**
![image.png](https://img-blog.csdnimg.cn/img_convert/820284e10c5020b4a8eca3d99510f809.png)
#### request.header 的getter和setter
请求头对象。这与 node [`http.IncomingMessage`](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb) 上的 [`headers`](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb) 字段相同
get header () {
return this.req.headers
},
set header (val) {
this.req.headers = val
},
#### request的属性是很多的,我们就不展开了,反正知道了原理,大家慢慢自己加吧。
### 封装response.js
对比request的封装,response的封装稍微有些不同,因为,对于request来说大部分的封装是getter,而response的封装大部分都是setter
在request部分我们阐述了三个使用getter和setter的原因。在resposne中最主要的原因我觉得是改变set的对象。
其实想一想很简单,例如在网络请求中我们会经常遇到各种状态:404 200等等,这些在node的http模块中,是用resposne.status进行改变的。假设我们在koa的response直接设置,你觉得会有用吗?**简单概括一句话:koa的request和respsone是对nodehttp模块的二次封装,并且底层还是对nodehttp模块的操作。**
#### response.status的getterh和setter
获取响应状态。默认情况下,`response.status` 设置为 `404` 而不是像 node 的 `res.statusCode` 那样默认为 `200`。
默认’404’,这里的默认是在何时默认的时候呢,其实是在接收到请求后就设置为404,也就是说在callback的时候开始设置为404。(**注意:http中res.statusCode用来标记状态码,在Koa中这个被封装成status**)
callback = (req, res) => {
let ctx = this.createContext(req, res)
const res = ctx.res
res.statusCode = 404
this.fn(ctx)
}
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**