Web 应用程序前端 ( 客户端 ) 与后端 ( 服务器端 ) 是如何进行数据交换的? 即: 客户端的数据怎么传到服务器端, 服务器端的数据又是怎么传到客户端? 这是初学 Web 应用程序开发时经常遇到的一个坎.
今天, 我们就来细聊一下这个问题.
本文假设你已经对前端技术 ( HTML / CSS / JavaScript ) 有所了解. 若假设不成立, 请先学习前述预备知识.
1. 术语
为统一认识, 先对一些基本术语作一些定义和解释, 不一定严谨, 大家明白就行…
客户端
用户所在的那一端. 通常用户使用电脑/手机 或 其它安装有
浏览器
( IE / Chrome / Firefox / …) 的设备来访问我们的网站, 所以我们把浏览器端称作客户端
.狭义的客户端可等同于浏览器. 广义而言, 微信小程序, Web App, Hybrid App 都可理解为客户端.
Web App - 可简单理解为适配手机的网站, 人称微网站.
Hybrid App - 混合/混生 App, 大部分功能由网页开发技术实现, 同时可借助宿主环境的提供的API与手机操作系统 (硬件系统) 进行交互.
服务器端
向前端(客户端)提供服务的那一端.
服务器端和前端主要交换两类资源:
(1) 静态资源: HTML / CSS / JavaScript / 图片…. 用户打开/刷新网页的时候从服务器下载到客户端.
(2) 数据: 就是”数据”, 比如用户填写的表单中的数据. 通常是双向的: 服务器 ←→ 客户端
对于传统的网站 和 Web App 而言, 服务器须前客户端提供上述2类资源
对于微信小程序 和 Hybrid App 而言, 在程序安装阶段, 其大部份静态资源已事先打包安装于用户的手机上了, 所以服务器端主要负责与客户端交换数据.
HTTP
超文本传输协议 ( Hypertext Transfer Protocol ) , 广泛应用于网站客户端与服务器端之间进行资源交换的传输协议.
在没有特别要求的情况下, 通常使用 HTTP 进行前后端通信. 若对信息安全有要求, 可使用 HTTPS 协议.
微信小程序正式发布版必须使用 HTTPS 协议. 开发环境下可使用 HTTP ( 要设置一下 ).
2. 准备开始
在正式开始之前再交待两句.
前/后端通信自然涉及到 前端程序
和 后端程序
的编写.
相对而言, 前端技术比较单一, 掌握 HTML / CSS / JavaScript 基本就可以通吃了.
而后端技术种类繁多, 喜欢 Java 的可以用 J2EE, 喜欢 PHP 的就用 PHP, 要是钟爱 JavaScript 也可以基于 Node.js 来实现后端程序. 或者你喜欢微软的技术, 那还可以选择 ASP / ASP.NET . 当然, 想装个 B 的话, 用 C 语言来实现后端程序也完全可以. 一般而言, 在一个项目里, 选用一种后端技术即可, 没必要搞得那么花哨.
本文以一个小例子为示例: 前端向后端发送2个数字 ( a 和 b ), 后端接收到后, 回应前端 a + b 的结果.
3. 客户端实现
客户端向服务器端传送数据, 通常有 3 种方式, 下面分别举例.
你可以先其中一种方式来实验, 成功后再实验其它方式.
3.1 在 URL 中携带数据
新建一个名为 sum-1.html 的文件, 写入如下代码, 保存.
1 |
|
上述代码中第 8 行是关键.
第 8 行的超链接 href
属性指定了此超链接点击后将转至 sum.action
, 同时携带2个参数 a = 3, b = 5
在浏览器中打开 sum-1.html 效果如下:
3.2 使用表单提交数据
新建一个名为 sum-2.html 的文件, 写入如下代码, 保存.
1 |
|
上述代码中第 8 ~ 12 行在网页中放入了一个表单 ( form ), form 标签的 action
属性指向 "/sum.action"
, 表明表单提交后数据将传输至 “/sum.action” 这个服务器端地址.
表单中有 3 个元素:
第 9 行 和 第 10 行使用 <input>
标签分别放入了两个输入框, 注意 input 标签中的 name
属性分别为 a 和 b, 它们是参数的名字, 在服务器端将使用这两个名字 ( a 和 b ) 取得前端传来的参数值.
第 11 行是一个 “提交按钮”, 注意其 type 属性为 “submit”, 点击即会提交表单, 同时携带2个参数 a 和 b.
在浏览器中打开 sum-2.html 效果如下:
3.3 使用 Ajax 方式提交数据
Ajax 是一种异步的数据通信方式, 在如今的网站开发中广泛使用. 关于 Ajax 的优势本文不表, 大家可以查阅其它资料, 或自己慢慢体会.
现在先跟着往下做…
新建一个名为 sum-3.html 的文件, 写入如下代码, 保存.
1 |
|
第 6 行, 引入 jQuery 库. 现在的浏览器本身都是支持 Ajax 技术的, 只是直接使用有点麻烦. 这里使用 jQuery, 它对Ajax 进行了封装, 使用起来比较简单.
jQuery 是什么鬼? 看这里: jQuery入门精要
本例中直接使用了
百度 CDN
提供的 jQuery 源, 省去了下载 jQuery 的麻烦, 但因为是使用的 Internet 上的资源, 所以调试时要记得接入Internet.
第 7 行引入我们自己的 JavaScript 代码文件 ( 一会儿创建 ).
第 10 行在页面上放置了一个 “普通按钮”, 点击此按钮将提交数据到服务器端. 注意此按钮的 type 属性无须设置为 submit. 按钮的 onclick 属性指明如果用户点击了此按钮, 将触发 doSum
这个函数的执行.
接着, 在与 sum-3.html 相同的文件夹下创建一个名为 sum-3.js 的文件. 代码如下:
1 | function doSum () { |
上述代码在 sum-3.js 中定义了一个函数 ( doSum ), 当点击页面中的 “求和” 按钮时将触发此函数的执行.
第 2 ~ 12 行使用 jQuery 封装的 Ajax 方法向服务器端发送数据.
第 3 行指定数据将要发送到哪里, 与 3.1 节第 8 行, 以及 3.2 节第 8 行指定的目的地相同.
第 9 ~ 11 行是一个 回调函数
, 当服务器端回传的计算结果会被放在第 9 行的 result 参数里, 接着在第 10 行以消息框的形式显示得到的计算结果.
在浏览器中打开 sum-3.html 的效果就不附图了, 自己试吧!
如果一切正常, 在浏览器中打开页面 sum-3.html, 点击其中的 “求和” 按钮应弹出消息框, 内容为 “我要准备发送数据到服务器端了哦~”. 如果 “不正常”, 请检查如下几个方面:
(1) sum-3.html 中第 6 行写对了吗? 你的电脑接入 Internet 了吗? 检查这一点, 确保正确引入了 jQuery 库
(2) sum-3.html 中第 7 行所引入外部 js 文件 ( sum-3.js ) 是否在正确的文件夹下, 名字取对了吗?
================================
如果你 3.1 ~ 3.3 节都跟着做了, 那么应该有如下图所示的这些文件. 注意它们是在一起的 ( 同一个文件夹下 )
算了, 以防万一, 还是把 源代码 附上吧, 有需要的自取. 不过还是强烈建议自己敲代码!
至此, 我们以 3 种不同的方式实现了客户端的程序, 算是完成了一半, 接下来还需要实现服务器的程序, 并正确部署, 这样客户端发送的数据才能正确被服务器端接收到, 客户端也才能正确接收到服务器端回应的数据. 即: 实现真正实现客户端与服务器端的信息交流.
4. 服务器端实现
如第 2 节中所述, 实现服务器端的技术有很多种, 具体选用哪一种, 看你喜欢.
本文将简介 2 种不同的技术实现: J2EE 和 Node.js.
如果你学过 Java, 可以先试试第 1 种方式. 如果没学过, 本博客中有 “Java 入门精要” 教程 :)
如果想试试使用 JavaScript 进行服务器端编程, 那可以自行传送到 4.2 节.
4.1 使用 J2EE 实现
关于 J2EE 开发环境的搭建, 可参看 IntelliJ IDEA + Tomcat 搭建Web应用程序开发环境 , Eclipse + Tomcat 搭建 Web 应用程序开发环境, 本文使用的是 IntelliJ IDEA + Tomcat.
4.1.1 创建 Web 项目
打开 IntelliJ IDEA , 选择菜单 File → New → Project … 创建一个 Web 应用程序项目.
注意上图中红色框的两个地方.
友情提醒: Tomcat 的版本应与 JDK 版本兼容 !!!
Next …
在接下来的窗口中随意输入一个你中意的项目名称 ( Project Name, 我写的是 “example427” )
Finish.
创建好的项目的目录结构应该大致如此:
其中 WEB-INF 那个目录如果没有自动创建出来也没关系.
4.1.2 创建 Servlet
现在, 创建一个 Servlet
用于响应前端的请求.
(1) 在 src 文件夹上点右键 → New → Package, 输入”servlets”, 创建一个包 ( Package )
作为一个 “成熟” 的程序员, 不要把 Java 代码直接放在 src 目录下, 所以我们在 src 下创建一个包(子文件夹)
包名叫什么不重要, 这里取名叫 servlets
(2) 在新建好的 servlets 包的图标上点右键 → New → Java Class, 输入 “SumServlet”, 创建一个 Java 源文件.
搞好后, 大概应该是这样子滴~
(3) 打开 SumServlet.java 文件 ( 可能已经自动打开了 ), 输入如下代码并保存.
1 | package servlets; |
解释一下吧~
上述代码创建一个名为 SumServlet 的 Servlet 用于与前端交互.
参看代码中的注释, 应能大概看懂其逻辑. 下面补充说明几点:
第 11 行, 使用 @WebServlet 注解 ( Annotation ) 声明 SumServlet 类为一个 Servlet, 即可以响应前端请求的入口. 其中的 urlPatterns = "/sum.action"
指明它所监听的前端请求的路径.
注意: 这里的 urlPatterns 的值要与 sum-1.html 第 8 行 / sum-2.html 第 8 行 / sum-3.js 第 3 行对应 !!!
第 12 行, 注意 SumServlet 类要继承 HttpServlet
第 14 ~ 29 行的 service
方法在接收到前端请求时会自动被执行, 形参表中的 req
参数带来了前端传来的信息, 服务器端要回传给前端的信息通过 resp
处理.
温馨提示: 如果你改动到了接口部分, 比如: urlPatterns, 而你的 Tomcat 已经处于运行状态, 那么你应该重新启动一下 Tomcat 才能看到修改后的效果.
4.1.3 组装前端代码
如果你跟着做完了第 3 节的内容, 你应该有一些 html / js 文件, 现在把它们都拷贝到项目的 web 文件夹下, 如下图:
闲聊两句~
一般而言, 如果你要做的是一个包含了前端程序和后端程序的完整 Web 项目, 那么你的前端代码应该要和后端代码在同一个 Web 项目下 ( 所以本节要求你把前端代码拷贝到项目的 web 文件夹下 ).
同时, 打开页面的方式不再是直接双击 html 文件图标, 而应该是在浏览器的地址栏中输入正确的访问路径 (见下节).
对于 微信小程序 / Hybrid App , 前端代码已经被打包在小程序 / App 里了, 也就是说, 前端程序和后端程序不可能是在同一个 Web 项目下. 此时更应该注意, 小程序 / App 所指向的服务器端访问地址应该是完整的URL, 即: 形如
http://xxxxx/xxxx
的样子 ( 带协议头 http / https ).同时, 因为微信小程序 / Hybrid App 与服务器端程序所在的 “域” 不同了, 所以从微信小程序 / Hybrid App 中向服务器端发送
POST
请求时还可能会受到 跨域资源共享 ( CORS ) 的限制, 导致请求失败.当然, 这不并是无解的问题, 只是出于安全性考虑, HTTP协议设置了这样的限制, 我们可以采用别的机制实现. ( 本文篇幅有限, 就不说了… 如果有必要, 请当面 @ 我, 我再写一篇博文专门讨论一下这个问题 )
本文为求简单, 所有3种方式的前端请求都使用 GET 方式发起. 因此, 不存在 CORS 问题, 放心…
4.1.4 Run / Debug
终于我们可以把程序跑起来看看效果了. 看到 IntelliJ IDEA 窗口右上角的那几个按钮了吗? ( 如下图 )
绿色三角以 运行模式
启动, 绿色虫虫以 调试模式
启动. 选一个你喜欢的, 戳它~
开发阶段最好以 “调试模式” 运行, 这样可以设断点来调试程序.
控制台哗啦哗啦出现一堆消息, 最后停下不动时就表示网站服务器已经启动完成了.
当然, 如果运气不好, 出现错误也是有可能滴~ 最好还是简单瞅一眼控制台的输出, 有没有什么异常 ( Exception )
**打开浏览器, 在地址栏输入 http://localhost:8080/sum-1.html **
应该可以看到 3.1 节做的那个页面, 如图:
点击超链接, 将会跳转至一个新的页面, 其中显示了 3 + 5 的结果: 8.0
3.2, 3.3 节中完成的另外两个页面也自己试试吧~ 浏览器地址栏中的地址把 sum-1.html 部分换成 sum-2.html / sum-3.html 即可.
3.3 节的示例因使用的是 Ajax 方式通信, 并不会导致页面跳转, 而是在当前页面弹出消息框, 显示计算结果.
如果你使用的是 Eclipse, 因为 Eclipse 的默认部署方式是带项目名称的, 所以访问路径还应加上项目名称, 如: http://localhost:8080/example426/sum-1.html.
好了, 但愿你一切顺利~ 阿门!
暂时想不到大家会掉到什么样的坑里, 如果进去了, 自己想想办法, 问问度娘, 实在不行, 给我写信 或 面聊.
最后, 附上完整的项目源代码, 还是那句话, 代码要一行行自己敲才有感觉!
4.2 使用 Node.js 实现
关于 Node.js 开发 Web 应用程序的环境搭建, 请参看 使用 Node.js 开发 Web 应用程序
4.2.1 创建项目
在你喜欢的地方创建一个文件夹 ( 项目文件夹 ), 此处我们取名为 “example426”.
以下是 Linux / CentOS / Mac OS … 操作系统下的控制台命令.
如果你使用 Windows 操作系统, 可以直接在 “资源管理器” / “ 我的电脑” 的地址栏输入 cmd
, 回车, 即可在当前文件夹位置打开控制台窗口, 当然也可以按键盘上的 Win + R
, 然后输入cmd
打开控制台窗口.
win 键在哪里? 自己百度一下吧~
1 | $ mkdir example426 |
上述代码中的 $
是命令提示符, 是不用手动输入的!
如果在 Windows 操作系统下, $ 的位置大概应该显示的是 C:\>
之类的样子.
好了, 这么简单的一件事情, 罗嗦这么半天… 就怕宝宝们看不懂, 我太难啦~
算了, 还是上个图吧:
好了, 终于可以解释一下上面那段命令的含意了…
第 1 行, 新建一个名叫 example426 的文件夹
第 2 行, 进入 example426 文件夹
第 3 行, 初始化项目. 在执行命令的过程中会问一些问题, 简单起见, 不解释了, 一路回车就行. 如下图:
注意图中红框所示位置, 它表示你程序的入口是项目根目录下的 index.js
这个文件.
第 3 行的命令执行完后, 执行第 5 行的命令, 在当前项目下安装 express
, 并保存配置.
express 是 Node.js 世界常用的 Web 应用程序开发框架.
执行第 5 行命令过程中会从 Internet 下载一些资源, 所以要接入Internet. 同时, 因为资源在国外, 所以会比较慢. 当然, 你可以配置 npm 使用国内镜像站点, 这样就可以飞起来了…
完成后在 example426 文件夹下应该会生成一个文件 package.json 的文件, 它是项目的配置文件, 暂时不用管它.
也许, 还会同时生成一个 package-lock.json 文件, 它是用来 “锁住” 项目依赖包的版本的.
同时, 还会生成一个 node_modules 文件夹, 里面装了项目依赖的东东.
上面三行话如果不明白, 当作没看见就行~
4.2.2 拷入前端代码
在 example426 文件夹下新建一个名为 public
的文件夹, 将前端文件 ( sum-1.html, sum-2.html, sum-3.html, sum-3.js ) 复制到 public 文件夹中.
4.2.3 创建入口文件
在 example426 文件夹下新建一个名为 index.js
的文件, 注意文件名要与执行 npm init 过程中的 entry point 一致. ( 如果你是像我一样, 一路回车, 那文件名就叫 index.js 即可 )
用你喜欢的文本编辑工具或开发工具 ( Windows 自带的记事本都行 ) 打开 index.js 文件, 敲入如入代码:
1 | const express = require('express'); // 引入 express 框架 |
套路! 全都是套路!
是不是发现上面这段代码和 4.1.2 节 SumServlet 中的代码长得很像? 自己看注释研究一下吧~
特别注意:
第 9 行, app.get(...)
监听的是前端 GET
请求, 如果前端发来的是 POST
方式的请求, 应改成 app.post(...)
第 11, 12 行, 取得 GET
请求中的参数使用 req.query
取得, 后面的 a / b 是参数名. 如果是 POST
的请求, 应换作 req.body
上个图, 到目前为止, example426 文件夹下的结构应该是这样滴:
4.2.4 Run / Debug
来吧~ 让程序跑起来!
回到控制台窗口, 在 example426 位置执行如下命令:
1 | $ node index.js |
如果一切正常, 应该看到这个…
**打开浏览器, 在地址栏输入 http://localhost:8088/sum-1.html **
应该可以看到 3.1 节做的那个页面, 点击超链接, 将会跳转至一个新的页面, 其中显示了 3 + 5 的结果: {“result”:8}
赶快试试另外的2个页面吧~
项目启动后, 应保持控制台窗口处于打开状态, 若要停止程序, 按 Ctrl + C
即可. 修改了代码后需要停止程序, 重新启动.
5. 后话
现在大家爽了吧, 赶快自己梳理一下, 整理一下心得.
补充几点:
(1) 本文中多次提到 GET
和 POST
请求, 这是 HTTP 协议中所规定的两种前端请求方法. 具体有什么不同, 参看: HTTP 方法:GET 对比 POST.
在 J2EE 中 可以统一使用 request.getParameter() 方式获取前端传来的参数.
但在多数的技术中 ( ASP / ASP.NET / PHP / Node.js … ) 前端请求的方式不同, 服务器端接收参数的方式也要随着变化 (参见4.2.3节特别注意部分). 否则会报 404 错误.
本文为求简单, 前端使用的都是 GET 方法, 但在实际的项目中, POST 方法使用更多.
(2) 若传输的数据结构复杂, 最好不要使用本文中这种 “离散” 的方式来传递, 如: a=3&b=5 …
应该使用”有格式”的结构化字符串来 “封装” 前后端交互的数据, 这样便于前后端处理.
例如, 可以使用 JSON 格式来封装上面的数据: { “a”: 3, “b”: 5 }
这时, 前端代码 sum-3.js 修改一下:
1 | function doSum () { |
注意 第 4 行, dataType 值改为 json, 告诉 jQuery 服务器端回传的数据是 JSON 格式的, 这样 jQuery 会自动将服务器端返回的数据按 JSON 格式进行解析, 并注入第 9 行 success 回调函数的 data 参数中. 也就是说, data 不再是普通的字符串, 而是一个 JavaScript 对象.
第 5 行, 使用 JSON.stringify ()
将发往服务器端的数据使用 JSON 格式封装 ( 序列化为 JSON 格式 ).
与此同时, 服务器端的代码也要改一下, 以 4.2.3 节中的代码为例:
1 | const express = require('express'); // 引入 express 框架 |
在 4.2.3 节代码基础上修改了 12 ~ 14 行.
注意第 12 行, 因为前端传来的数据被封装成了 JSON 格式字符串, 使用了 params
这个名字, 所以此处从 req.query.params 中取得前端传来的参数, 但因为是 JSON 格式字符串, 所以还使用 JSON.parse() 进行了解析. ( 请对照本节中 sum-3.js 第 5 行仔细研究一下 )
第 24 行, 回传给前端的数据是一个对象: { result: result } , 它会被 express 自动序列化为 JSON 格式.
如果你使用 J2EE 实现服务器端程序, 可以引入第 3 方工具包 ( 比如: gson, fastjson ) 来进行 JSON 的序列化和反序列化.
人困马乏, 天都亮了, 就暂不举例了, 自行百度一下~ 挖个坑, 改天有空再补一个例子.
传送门: 文件上传怎么做
Revised on 2021/11/08 01:48:04 by Bailey
-
Next PostLinux 安装 MySQL
-
Previous Post文件上传怎么做 (上)