前端服务与后端服务的搭配方式
我们打开浏览器的调试工具,切换到网络Tab,可以看到有各种各样的请求类型,包括 HTML,CSS,JS,字体,Image,媒体,清单,WS,WASM,其他等。这些都是网页里要展示的内容和数据,由远在天边的服务器返回。
Web前端的发展
在早期 Web 服务中,前后端功能都比较简单,文件是静态文件,由后端服务程序负责返回,比如nginx,Apache等。
后面 Web 2.0 迅速发展,网页内容变成了动态的,网页只是个框架,内容是存在数据库里的,诞生了各种 P 技术,JSP/PHP/ASP等。这些服务,读取网页文件+读取数据库里的数据,拼接成完整的网页,返回给浏览器。点击网页里的超链接加载下一页、或者提交一个评论,都会打开新的页面,可能是新的tab或者当前窗口,但都是加载一个新的页面。
随着 Google Map 和 Gmail 的发展,在一个页面里对地图进行各种操作放大/缩小/加载更多的信息,为了操作更加连贯,进行动态变化,而不是刷新页面,衍生出了Ajax请求,可以在网页里动态请求数据,然后动态的修改页面的部分。
此后,在电脑及手机设备的配置增强,及浏览器JS引擎的性能提升,以及 Node.js 诞生的背景下,又发展出了单页面应用,Single Page Application, SPA。这种 技术使得看起来是很多个页面,像是一个桌面/手机应用的网站,背后却只有一个HTML,它的各种展示都是由JavaScript动态绘制的。
后面 Node.js 与 单页面应用的发展阶段
| 发展阶段 | Node.js | 单页面应用 |
|---|---|---|
| 2009-2012 | 提供高并发后端 | 适用于 API 服务器,但与前端关系不大 |
| 2013-2016 | 构建工具(Grunt/Gulp/Webpack) | 前端使用 Node.js 进行工程化 |
| 2016-2020 | SSR、GraphQL、Serverless | 单页面应用开始依赖 Node.js 运行 |
| 2020 至今 | Edge Computing、Deno、Bun | 前端和后端进一步融合 |
前端分工越来越重要,成为独立的职位。
前端服务和后端服务
因为Node.js的加持,前端工程师的工作,不只是在停留在浏览器里,也拓展到桌面端,服务端。比如可以自己启动服务,进行本地开发、热更新,然后还可以自己进行部署服务。
前端本地开发
前端单页面应用,在本地开发的时候会使用构建工具进行构建,构建的产物交给浏览器运行 。其中有两种方式:
方式1️⃣ 构建工具启动服务,浏览器打开的页面由构建工具提供;页面内请求的接口直接请求后端服务或者由前端服务代理到后端服务。
方式2️⃣ 构建工具把打包的内容交给后端服务,浏览器打开的页面由后端服务提供,后端服务把打包的内容返回给前端。
以 webpack 举例,上面说的两种方式对应的是 webpack-dev-server 和 webpack-dev-middleware
webpack-dev-server 是一个基于 Express 的开发服务器,提供静态资源服务,并支持热更新(HMR)。它内置了 webpack-dev-middleware,因此可以直接监听文件变更并重新编译;代理(Proxy):可以代理 API 请求到后端服务器,避免跨域问题。
webpack-dev-middleware 只是一个 Express 中间件,它将 Webpack 编译后的资源提供给 Express,而不会启动一个完整的开发服务器。适用于希望自定义服务器(如基于 Koa、Express)并结合其他功能的场景。需要手动实现热更新,和其他静态文件。更繁琐,也更灵活,可以自由配置。
生产环境部署
为了简单,下面的一些服务用某种语言代指,比如后端主要业务使用Java Spring 服务,前端开发使用 Node.js Express 服务开 发
实际部署时,静态文件一般会放在静态文件服务器或OSS服务内。
服务的管理可能采用的方式:
- Node.js 起服务,仅提供页面,接口使用不同的域名跨域访问Java服务
- Node.js 起服务,提供页面,接口是同域下,代理给Java服务
- Java 服务同时提供接口和页面
这两者会有哪些差异:Cookie,鉴权,单点登录,重定向,跨域,服务器渲染
| 方式 | 分离式 浏览器访问 Node 和 Java | 共用式 浏览器只访问 Node,接口代理给 Java | 共用式 浏览器只访问 Java 服务 |
|---|---|---|---|
| 成本 | ❌管理两个服务器,成本较高 | ❌管理两个服务器,成本较高 | ✅只需要一个服务器,运维成本低。 |
| 服务器压力 | ✅分别较低 | ❌Node服务压力大,虽不处理逻辑,仅做代理但需要保持连接 | ❌Java服务压力大,既要处理 API 请求,又要提供静态文件。 |
| 跨域问题 | ❌存在跨域问题 | ✅不存在 | ✅不存在 |
| 重定向 | ❌Java服务无法控制重定向,需要前端配合 | ❌不方便控制重定向,需要Node代理服务开发 | ✅Java服务可以直接控制页面跳转 |
| 鉴权(Java服务负责) | ❌不易使用Cookie的方式,适用Token/JWT,前端在登录后拿到 JWT,存储在 localStorage,每次请求带上。 | ✅可用 Cookie + Session 的方式 | ✅可用 Cookie + Session 的方式 |
| OAuth 登录 | ❌前端请求接口获取id_token,交给后端验证,前后端配合 | ❌不方便控制重定向,需要Node代理服务开发 | ✅通过重定向机制,Java服务直接拿到id_token进行验证,通过后直接修改登录状态 |
| 服务端渲染 | ✅便于支持SSR | ✅便于支持SSR | ❌不便于服务端渲染 |
| 前后端解藕 | ✅前后端完全解耦,独立开发、测试、部署,团队协作更灵活。 | ❌不完全解藕 | ❌不完全解藕 |
总结
- 中间这种方式不太适用,徒增一层代理,费力不讨好,只是解决了跨域问题;
- 两侧的方式各有利弊
- 在单体应用的年代,用统一的Java服务更为简单;
- 而如今前后端完全分离的应用更为广泛,原因有:
- 微服务更为普及,业务端使用JWT进行鉴权,与App/小程序共用相同的逻辑,更简单一致;
- 同时前后端解藕,界线更为清晰,前后端开发起来都更快捷高效;
- Node.js 服务可以组装数据,为网页提供服务端渲染