12.服务器端JavaScript
12.1用rhino脚本化JavaScript
官网:https://github.com/mozilla/rhino
https://github.com/mozilla/rhino/releases/tag/Rhino1_7_11_RC2_Release
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" -jar rhino-1.7.11-RC2.jar download.js
importPackage(javax.swing);
importClass(javax.swing.border.EmptyBorder);
importClass(java.awt.event.ActionListener);
importClass(java.net.URL);
importClass(java.io.FileOutputStream);
importClass(java.lang.Thread);
// 创建一些GUI小部件(widget)
var frame = new JFrame("Rhino URL Fetcher");
var urlField = new JTextField(30);
var button = new JButton("Download");
var fileChooser = new JFileChooser();
var row = Box.createHorizontalBox();
var col = Box.createVerticalBox();
var padding = new EmptyBorder(3, 3, 3, 3);
// 把它们组装一起并显示这个GUI
row.add(urlField);
row.add(button);
col.add(row);
frame.add(col);
row.setBorder(padding);
frame.pack();
frame.setVisible(true);
// 当窗体中发生任何事件都会调用这个函数
frame.addWindowListener(new java.awt.event.WindowAdapter() {
windowClosing: function (e) {
java.lang.System.exit(0);
}
});
// 当用户单击按钮时,调用这个函数
button.addActionListener(function () {
try {
// 创建java.net.URL表示源URL
var url = new URL(urlField.getText());
// 告诉用户选择保存URL内容的文件
var response = fileChooser.showSaveDialog(frame);
// 如果单击Cancel按钮,立即退出
if (response != JFileChooser.APPROVE_OPTION) return;
// 否则,获取java.io.File表示目标文件
var file = fileChooser.getSelectedFile();
// 现在启动一个新线程下载URL
new Thread(function () { download(url, file); }).start();
} catch (e) {
// 如果出现错误,显示一个对话框
JOptionPane.showMessageDialog(frame, e.message, "Exception", JOptionPane.ERROR_MESSAGE);
}
});
// 使用java.io.File等把内容保存到一个文件中
// 使用java.net.URL等下载URL的内容,在JProgressBar组件中显示下载进度
// 这将在一个新线程中调用
function download(url, file) {
try {
// 每次下载一个URL时,我们会添加一个新的数据行到窗体中
// 数据行中会显示URL、文件名和下载进度
var row = Box.createHorizontalBox();
row.setBorder(padding);
var label = url.toString() + ":";
row.add(new JLabel(label));
var bar = new JProgressBar(0, 100);
bar.setStringPainted(true);
bar.setString(file.toString());
row.add(bar);
col.add(row);
frame.pack();
// 我们不知道URL的大小,所以进度条是动画
bar.setIndeterminate(true);
var conn = url.openConnection();
conn.connect();
var len = conn.getContentLength();
if (len > 0) {
// 如果能得到URL长度就设置
bar.setMaximum(len);
bar.setIndeterminate(false);
}
// 下载的百分比
var input = conn.getInputStream();
var output = new FileOutputStream(file); // 把字节写入文件
var buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 4096);
var num;
while ((num = input.read(buffer)) != -1) { // 读取然后循环至EOF
output.write(buffer, 0, num); // 把字节写入文件
bar.setValue(bar.getValue() + num); // 更新进度条
}
output.close();
input.close();
} catch (e) { // 如果发生错误,在进度条上显示错误
if (bar) {
bar.setIndeterminate(false);
bar.setString(e.toString());
}
}
}
12.2 用node实现异步i/o
12.2.1 node示例:http服务器
// 首先,加载所有要用的模块
var http = require('http');
var fs = require('fs');
var url = require('url');
// 创建新的HTTP服务器
var server = new http.Server();
server.listen(8000); // 在端口8000上运行它
// Node使用"on()"方法注册事件处理程序,当服务器得到新请求,则运行函数处理它
server.on("request", function(request, response) {
// 解析请求的URL
var parsedUrl = url.parse(request.url, true);
// 特殊URL会让服务器在发送响应前先等待,此处用于模拟缓慢的网络连接
if (parsedUrl.pathname === "/test/delay") {
// 使用查询字符串来获取延迟时长,或者2000毫秒
var delay = parseInt(parsedUrl.query.delay) || 2000;
// 设置响应状态码和头
response.writeHead(200, {"Content-Type": "text/plain; charset=UTF-8"});
// 立即开始编写响应主体
response.write("sleeping for " + delay + " milliseconds...");
// 在之后调用的另一个函数中完成响应
setTimeout(function() {
response.write("done.");
response.end();
}, delay);
// 若请求是"/test/mirror”,则原文返回它
// 当需要看到这个请求头和主体时,会很有用
} else if (parsedUrl.pathname === "/test/mirror") {
// 响应状态和头
response.writeHead(200, {"Content-Type": "text/plain; charset=UTF-8"});
// 用请求的内容开始编写响应主体
response.write(request.method + " " + request.url + " HTTP/" + request.httpVersion + "\r\n");
// 所有的请求头
for (var h in request.headers) {
response.write(h + ": " + request.headers[h] + "\r\n");
}
response.write("\r\n"); // 使用额外的空白行来结束头
// 把请求体写回响应
request.pipe(response);
// 否则,处理来自本地目录的文件
} else {
// 获取本地文件名,基于其扩展名推测内容类型
var filename = parsedUrl.pathname.substring(1); // 去掉前导"/"
var type;
switch (filename.substring(filename.lastIndexOf(".") + 1)) { // 扩展名
case "html":
case "htm":
type = "text/html; charset=UTF-8";
break;
case "js":
type = "application/javascript; charset=UTF-8";
break;
case "css":
type = "text/css; charset=UTF-8";
break;
case "txt":
type = "text/plain; charset=UTF-8";
break;
case "manifest":
type = "text/cache-manifest; charset=UTF-8";
break;
default:
type = "application/octet-stream";
break;
}
// 异步读取文件,并将内容作为单独的数据块传给回调函数
// 对于确实很大的文件,使用流API fs.createReadStream()更好
fs.readFile(filename, function(err, content) {
if (err) { // 如果由于某些原因无法读取该文件
response.writeHead(404, { "Content-Type": "text/plain; charset=UTF-8" }); // 发送404未找到状态码
response.write(err.message); // 简单的错误消息主体
response.end(); // 完成
} else { // 否则,若读取文件成功
response.writeHead(200, { "Content-Type": type }); // 设置状态码和MIME类型
response.write(content); // 把文件内容作为响应主体发送
response.end(); // 完成
}
});
}
});
运行代码
- 确保你已经安装了Node.js:可以通过以下命令检查:
node -v
如果没有安装,可以从Node.js官网下载并安装。
- 保存代码:将上述修正后的代码保存到一个文件中,例如
server.js
。 - 运行服务器:在终端中导航到包含
server.js
的目录,然后运行以下命令:
node server.js
测试服务器
- 打开浏览器并访问
http://localhost:8000/test/delay?delay=3000
来测试延迟响应。 - 访问
http://localhost:8000/test/mirror
来测试镜像请求。 - 访问
http://localhost:8000/filename
来测试文件服务(替换filename
为实际存在的文件名)。
- 打开浏览器并访问
12.2.2 node示例:http客户端工具模块
app.js
const httputils = require('./httputils'); // 引入httputils模块
// 测试GET请求
const url = 'http://www.example.com'; // 替换为你要请求的URL
httputils.get(url, function(status, headers, body) {
console.log('GET Request:');
console.log('Status:', status);
console.log('Headers:', headers);
console.log('Body:', body);
});
// 测试POST请求
const postData = { key1: 'value1', key2: 'value2' }; // 替换为你要发送的数据
httputils.post(url, postData, function(status, headers, body) {
console.log('POST Request:');
console.log('Status:', status);
console.log('Headers:', headers);
console.log('Body:', body);
});
httputils.js
const http = require('http');
const url = require('url');
const querystring = require('querystring');
// 实现一个异步HTTP GET请求,并将HTTP状态、头和响应主体传递给指定的回调函数
exports.get = function(urlString, callback) {
// 解析URL,获取所需的信息
const parsedUrl = url.parse(urlString);
const hostname = parsedUrl.hostname;
const port = parsedUrl.port || 80;
let path = parsedUrl.pathname;
const query = parsedUrl.query;
if (query) path += '?' + query;
// 实现一个简单的GET请求
const options = {
hostname: hostname,
port: port,
path: path,
method: 'GET',
headers: {
"Host": hostname // Request headers
}
};
const req = http.request(options, function(response) {
response.setEncoding("utf8"); // 设置编码,使返回的主体成为文本而非字节
let body = ""; // 一旦响应主体达到,保存它
response.on("data", function(chunk) {
body += chunk;
});
response.on("end", function() {
if (callback) callback(response.statusCode, response.headers, body);
});
});
req.on("error", function(e) {
console.error(`Problem with request: ${e.message}`);
});
req.end();
};
// 以数据作为请求主体的简单HTTP POST请求
exports.post = function(urlString, data, callback) {
// 解析URL,获取所需的信息
const parsedUrl = url.parse(urlString);
const hostname = parsedUrl.hostname;
const port = parsedUrl.port || 80;
let path = parsedUrl.pathname;
const query = parsedUrl.query;
if (query) path += '?' + query;
// 判断将要作为请求主体发送的数据类型
let type;
if (data == null) {
data = '';
type = "text/plain;charset=UTF-8";
} else if (data instanceof Buffer) {
// 二进制数据
type = "application/octet-stream";
} else if (typeof data === "string") {
// 字符串数据
type = "text/plain;charset=UTF-8";
} else if (typeof data === "object") {
// 名/值对
data = querystring.stringify(data);
type = "application/x-www-form-urlencoded";
}
// 生成POST请求,其中包括请求主体
const options = {
hostname: hostname,
port: port,
path: path,
method: 'POST',
headers: {
"Host": hostname,
"Content-Type": type,
"Content-Length": Buffer.byteLength(data)
}
};
const req = http.request(options, function(response) {
response.setEncoding("utf8"); // 假设它是文本
let body = ""; // 用于保存响应主体
response.on("data", function(chunk) {
body += chunk;
});
response.on("end", function() {
// 完成后,调用回调函数
if (callback) callback(response.statusCode, response.headers, body);
});
});
req.on("error", function(e) {
console.error(`Problem with request: ${e.message}`);
});
req.write(data); // 发送请求主体
req.end();
};
node app.js
GET 请求:
httputils.get
方法发起一个 GET 请求。- 它解析 URL,创建一个 HTTP 请求,并在接收到响应时调用回调函数。
- 回调函数打印出状态码、响应头和响应主体。
POST 请求:
httputils.post
方法发起一个 POST 请求。- 它解析 URL,确定请求主体的数据类型,创建一个 HTTP 请求,并在接收到响应时调用回调函数。
- 回调函数打印出状态码、响应头和响应主体。
这样,你就可以使用
httputils.js
模块来发起异步的 HTTP GET 和 POST 请求,并处理服务器的