首页
关于
翻译
留言
统计
搜索
1
以太坊简介
744 阅读
2
搭建 OpenAI 代理
683 阅读
3
第 4 章 创建您的第一个 React 组件
554 阅读
4
如何读懂编译后的 JavaScript 代码
536 阅读
5
第 9 章 使用 Jest 测试 React 应用
477 阅读
JavaScript
TypeScript
后端
Web
移动
运维
杂项
登录
Search
标签搜索
React
翻译
Vue
组件
Angular
工程化
库
Hook
框架
优化
路由
Node.js
Flash
部署
算法
可视化
Debug
测试
兼容
Web3
Flying
累计撰写
267
篇文章
累计收到
2
条评论
首页
栏目
JavaScript
TypeScript
后端
Web
移动
运维
杂项
页面
关于
翻译
留言
统计
搜索到
15
篇
Node.js
相关的结果
2023-06-18
Mern 身份验证 API
在本文中,我们将从零开始创建一个 MERN 栈应用程序。此项目的视频可以在此处找到。前端将使用 React、Redux、React Router 和 React Bootstrap 构建单页面应用程序。后端将使用 Express、MongoDB 和 Mongoose 作为数据库,并使用 JWT(JSON Web Tokens)和 HTTP-only Cookie 来实现身份验证。我们还将使用 Redux 进行状态管理,并使用 Redux Toolkit 使事情变得更加简单。本文将分为两个部分。第一部分我们将创建后端,第二部分我们将创建前端。
2023年06月18日
130 阅读
0 评论
1 点赞
2023-04-08
搭建 OpenAI 代理
目前在国内访问 OpenAI 还是需要“科学”上网的,更不用说调用 OpenAI API。所以我尝试用 Node.js 写了个代理小程序,并部署在 Codesandbox 上,然后就可以在本地用习惯的前后端分离的方式调用 OpenAI API。使用比较简单,特写本文和大家分享。好用话请帮忙 Star 一下我的 repo。获取 API 密钥本文假设我们已经注册了 OpenAI 账号,如果还没有创建 API 密钥,可以如下图进行操作。OpenAI 注册文章一大堆,最关键的是要输入一个可用的手机号完成验证,没有的话可租用,我是花了 12 卢布,不到人民币 2 元,所以自己动动手,真没必要花几十元买奸商的账号。代理实现其实多数语言都能模拟客户端调用实现类似网络爬虫,这里仅以Node.js为例。实现代码比较简单,主要是用 express 和 http-proxy-middleware 实现的。// ... app.use(express.static(path.join(__dirname, "public"))); app.use( "/v1/", createProxyMiddleware({ target: "https://api.openai.com", changeOrigin: true, onProxyReq: (proxyReq, req, res) => { console.log(req.originalUrl); proxyReq.setHeader("Authorization", `Bearer ${process.env.API_KEY}`); }, onProxyRes: (proxyRes, req, res) => { proxyRes.headers["Access-Control-Allow-Origin"] = "*"; proxyRes.headers["Access-Control-Allow-Headers"] = "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"; } }) ); // ...完整示例代码参看 repo 中的 index.js。createProxyMiddleware 的用法就不细说了。有几点需要注意:onProxyReq 事件回调中我们设置了请求标头 Authorization,将 API 密钥保存到了代理代码中,这样前端调用时就不用加代理验证了。但问题来了,我们的将代码部署到国外主机上安全吗?这就担心过头了。一来目前注册一个账号就买一根冰棒的钱,二来很多云平台可以使用环境变量配置 API 密钥。幸运的是,Codesandbox 就可以。onProxyReq 事件回调中我们通过设置了响应标头 Access-Control-Allow-Origin 为 *,这意味着任何人都可以访问我们的代理服务。当然如果不想被“白嫖”,我们可以根据需要固定几个IP。本地调试时,记得“科学”上网,还要将 ${process.env.API_KEY} 替换成你自己的 KEY。出于安全,官方不建议使用前后端分离方式开发 OpenAI Web 应用,我们搭建的代理刚好能回避这个问题。验证当然最好是用页面来验证,以下代码了演示怎样使用 axios 调用我们之前搭建的代理服务。 axios.post('http://localhost:3000/v1/completions', { model: 'text-davinci-003', max_tokens: 1024, prompt: prompt }) .then(response => { // ... }) .catch(error => { // ... })完整示例代码参看 repo 中的 public/index.html,这应该算是最简单的纯前端 OpenAI 应用了。和普通 Ajax 调用没啥区别。当然也可以使用官方提供的 openai 之类的库,只要将 basePath 设置代理服务地址(如本示例中的 http://localhost:3000/v1)即可。Node.js 版本的 openai 的其实是依赖 axios 的,社区的各种库也只是基于某种语言或业务场景对 OpenAI API 的封装,所以看看 OpenAI API 文档没啥难度。访问不了文档的可以让 ChatGPT 来帮忙?。API 常见的就是 Ajax 请求了。OpenAI API 也不例外。后端对界面不太关注的同学用 curl、postman 之类的工具也可以验证。免费部署代码测试好后还要部署在国外才行。目前,免费部署 Node.js 应用的云平台并不是很多,推荐 Codesandbox。CodesandboxCodesandbox 简直就是一个神器,我们不仅可以使用它来写代码,还能在上面托管应用,甚至是 Node.js 应用。登录 Codesandbox 账号没有 Codesandbox 账号的话最好关联自己的 Github 注册一个账号,以后我们就可以在这两个平台之间同步自己的项目了,非常方便。新建工程单击 “Create”,选择一个 Node.js 模板,新建 Node.js 工程。添加依赖在 package.json 中添加以下依赖:"dependencies": { "express": "^4.18.2", "http-proxy-middleware": "^2.0.6" }替换代码将 index.js 中的代码替换为完整的代理代码。可以从我的 repo 获取。如果觉得前面几步操作麻烦,可以直接 Fork 一下我的示例项目,然后继续。配置 API 密钥左上角菜单 > “Project Settings” > “Env Variables”,填入先前生成的 API 密钥,如下图所示:重启服务左上角菜单 > “Restart Sandbox”重启服务,若能在右侧看到以下预览页面说明部署没大问题。预览页面地址栏显示的就是最终生成的代理服务地址。如本项目的 https://ge68te-3000.csb.app。如果不打算分享服务,可以将 CodeSandbox 项目移动到草稿中私有化,以提高服务的安全性。其他选择VencelVencel 部署的应用目前国内要“科学”上网才可以访问,不推荐。Netlify国内倒是能访问,不过官方不支持 Express 模板,要用 Netlify function“曲线救国”,操作起来有些麻烦。还有部署生成服务地址也感觉怪怪的,所以也不推荐。其他平台像 AWS、腾讯云等平台也可以部署 Node.js 项目,但有一定的免费期限。我们的程序很简单,有点杀鸡用牛刀的感觉。当然最好的办法是使用 VPN 访问或搞到国外主机,不过俺是穷人。?总结直接 Fork我的示例项目使用环境变量配置 API 密钥重启服务就搭建好了,很简单吧?以前老觉得 AI 高深莫测,打通 OpenAI API 的“任督二脉”之后, 我们应该对 AI 开发充满信心。
2023年04月08日
683 阅读
0 评论
2 点赞
2022-01-25
Node.js 应用安全最佳实践
Node.js 的优势之一是能够安装额外的模块,从安全的角度来看,这提供了更多打开后门的机会。此外,框架越流行,黑客尝试发现漏洞的机会就越大。因此,您应该始终认真对待 Node.js 的安全性。在这篇文章中,您将了解保护 Node.js 应用程序的 11 种最佳实践。
2022年01月25日
114 阅读
0 评论
1 点赞
2020-01-23
Express 中的 Request 对象
req对象代表了一个 HTTP 请求,其具有一些属性来保存请求中的一些数据,比如query string,parameters,body,HTTP headers等等。按照惯例,这个对象总是简称为req(http 响应简称为res),但是它们实际的名字由这个回调方法在那里使用时的参数决定。如下例子:app.get('/user/:id', function (req, res) { res.send('user' + req.params.id); });其实你也可以这样写:app.get('/user/:id', function (request, response) { response.send('user' + request.params.id); });属性在Express 4中,req.files默认在req对象中不再是可用的。为了通过req.files对象来获得上传的文件,你可以使用一个multipart-handling(多种处理的工具集)中间件,比如busboy,multer,formidable,multipraty,connect-multiparty或者pez。req.app这个属性持有express程序实例的一个引用,其可以作为中间件使用。如果你按照这个模式,你创建一个模块导出一个中间件,这个中间件只在你的主文件中require()它,那么这个中间件可以通过req.app来获取 express 的实例。例如:// index.js app.get('/viewdirectory', require('./mymiddleware.js'));// mymiddleware.js module.exports = function (req, res) { res.send('The views directory is ' + req.app.get('views')); };req.baseUrl一个路由实例挂载的 Url 路径。var greet = express.Router(); greet.get('/jp', function (req, res) { console.log(req.baseUrl); // greet res.send('Konichiwa!'); }); app.use('/greet', greet);即使你使用的路径模式或者一系列路径模式来加载路由,baseUrl属性返回匹配的字符串,而不是路由模式。下面的例子,greet路由被加载在两个路径模式上。app.use(['/gre+t', 'hel{2}o'], greet); // load the on router on '/gre+t' and '/hel{2}o'当一个请求路径是/greet/jp,baseUrl是/greet,当一个请求路径是/hello/jp,req.baseUrl是/hello。req.baseUrl和app对象的mountpath属性相似,除了app.mountpath返回的是路径匹配模式。req.body在请求的 body 中保存的是提交的一对对键值数据。默认情况下,它是undefined,当你使用比如body-parser和multer这类解析body数据的中间件时,它是填充的。下面的例子,给你展示了怎么使用body-parser中间件来填充req.body。var app = require('express'); var bodyParser = require('body-parser'); var multer = require('multer');// v1.0.5 var upload = multer(); // for parsing multipart/form-data app.use(bodyParser.json()); // for parsing application/json app.use(bodyParser.urlencoded({extended:true})); // for parsing application/x-www-form-urlencoded app.post('/profile', upload.array(), function(req, res, next) { console.log(req.body); res.json(req.body); });req.cookies当使用cookie-parser中间件的时候,这个属性是一个对象,其包含了请求发送过来的cookies。如果请求没有带cookies,那么其值为{}。// Cookie:name=tj req.cookies.name; // => "tj"获取更多信息,问题,或者关注,可以查阅cookie-parser。req.fresh指示这个请求是否是新鲜的。其和req.stale是相反的。当cache-control请求头没有no-cache指示和下面中的任一一个条件为true,那么其就为true:if-modified-since请求头被指定,和last-modified请求头等于或者早于modified响应头。if-none-match请求头是*。if-none-match请求头在被解析进它的指令之后,和etag响应头的值不相等ps:If-None-Match 作用:If-None-Match 和 ETag 一起工作,工作原理是在 HTTP Response 中添加 ETag 信息。 当用户再次请求该资源时,将在 HTTP Request 中加入 If-None-Match 信息(ETag 的值)。如果服务器验证资源的 ETag 没有改变(该资源没有更新),将返回一个 304 状态告诉客户端使用本地缓存文件。否则将返回 200 状态和新的资源和 Etag. 使用这样的机制将提高网站的性能req.fresh; // => truereq.hostname包含了源自HostHTTP 头部的hostname。当trust proxy设置项被设置为启用值,X-Forwarded-Host头部被使用来代替Host。这个头部可以被客户端或者代理设置。// Host:"example.com" req.hostname; // => "example.com"req.ips当trust proxy设置项被设置为启用值,这个属性包含了一组在X-Forwarded-For请求头中指定的 IP 地址。不然,其就包含一个空的数组。这个头部可以被客户端或者代理设置。例如,如果X-Forwarded-For是client,proxy1,proxy2,req.ips就是["clinet", "proxy1", "proxy2"],这里proxy2就是最远的下游。req.originalUrlreq.url不是一个原生的Express属性,它继承自Node's http module。这个属性很像req.url;然而,其保留了原版的请求链接,允许你自由地重定向req.url到内部路由。比如,app.use()的mounting特点可以重定向req.url跳转到挂载点。// GET /search?q=something req.originalUrl; // => "/search?q=something"req.params一个对象,其包含了一系列的属性,这些属性和在路由中命名的参数名是一一对应的。例如,如果你有/user/:name路由,name属性可作为req.params.name。这个对象默认值为{}。// GET /user/tj req.params.name; // => "tj"当你使用正则表达式来定义路由规则,捕获组的组合一般使用req.params[n],这里的n是第几个捕获租。这个规则被施加在无名通配符匹配,比如/file/*的路由:// GET /file/javascripts/jquery.js req.params[0]; // => "javascripts/jquery.js"req.path包含请求 URL 的部分路径。// example.com/users?sort=desc req.path; // => "/users"当在一个中间件中被调用,挂载点不包含在req.path中。你可以查阅app.use()获得跟多的信息。req.protocol请求的协议,一般为http,当启用 TLS 加密,则为https。当trust proxy设置一个启用的参数,如果存在X-Forwarded-Proto头部的话,其将被信赖和使用。这个头部可以被客户端或者代理设置。req.ptotocol; // => "http"req.query一个对象,为每一个路由中的query string参数都分配一个属性。如果没有query string,它就是一个空对象,{}。// GET /search?q=tobi+ferret req.query.q; // => "tobi ferret" // GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse req.query.order; // => "desc" req.query.shoe.color; // => "blue" req.query.shoe.type; // => "converse"req.route当前匹配的路由,其为一串字符。比如:app.get('/user/:id?', function userIdHandler(req, res) { console.log(req.route); res.send('GET'); });前面片段的输出为:{ path:"/user/:id?" stack: [ { handle:[Function:userIdHandler], name:"userIdHandler", params:undefined, path:undefined, keys:[], regexp:/^\/?$/i, method:'get' } ] methods:{get:true} }req.secure一个布尔值,如果建立的是 TLS 的连接,那么就为true。等价与:'https' == req.protocol;req.signedCookies当使用cookie-parser中间件的时候,这个属性包含的是请求发过来的签名cookies,这个属性取得的是不含签名,可以直接使用的值。签名的cookies保存在不同的对象中来体现开发者的意图;不然,一个恶意攻击可以被施加在req.cookie值上(它是很容易被欺骗的)。记住,签名一个cookie不是把它藏起来或者加密;而是简单的防止篡改(因为签名使用的加密是私人的)。如果没有发送签名的cookie,那么这个属性默认为{}。// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3 req.signedCookies.user; // => "tobi"为了获取更多的信息,问题或者关注,可以参阅cookie-parser。req.stale指示这个请求是否是stale(陈旧的),它与req.fresh是相反的。更多信息,可以查看req.fresh。req.stale; // => truereq.subdomains请求中域名的子域名数组。// Host:"tobi.ferrets.example.com" req.subdomains; // => ["ferrets", "tobi"]req.xhr一个布尔值,如果X-Requested-With的值为XMLHttpRequest,那么其为true,其指示这个请求是被一个客服端库发送,比如jQuery。req.xhr; // => true方法req.accepts(types)检查这个指定的内容类型是否被接受,基于请求的Accept HTTP 头部。这个方法返回最佳匹配,如果没有一个匹配,那么其返回undefined(在这个 case 下,服务器端应该返回 406 和"Not Acceptable")。type值可以是一个单的MIME type字符串(比如application/json),一个扩展名比如json,一个逗号分隔的列表,或者一个数组。对于一个列表或者数组,这个方法返回最佳项(如果有的话)。// Accept:text/html req.accepts('html'); // => "html" // Accept:text/*, application/json req.accepts('html'); // => "html" req.accepts('text/html'); // => "text/html" req.accepts(['json', 'text']); // => "json" req.accepts('application/json'); // => "application/json" // Accept:text/*, application/json req.accepts('image/png'); req.accepts('png'); // => undefined // Accept:text/*;q=.5, application/json req.accepts(['html', 'json']); // => "json"获取更多信息,或者如果你有问题或关注,可以参阅accepts。req.acceptsCharsets(charset[, ...])返回指定的字符集集合中第一个的配置的字符集,基于请求的Accept-CharsetHTTP 头。如果指定的字符集没有匹配的,那么就返回 false。获取更多信息,或者如果你有问题或关注,可以参阅accepts。req.acceptsEncodings(encoding[, ...])返回指定的编码集合中第一个的配置的编码,基于请求的Accept-EncodingHTTP 头。如果指定的编码集没有匹配的,那么就返回 false。获取更多信息,或者如果你有问题或关注,可以参阅accepts。req.acceptsLanguages(lang [, ...])返回指定的语言集合中第一个的配置的语言,基于请求的Accept-LanguageHTTP 头。如果指定的语言集没有匹配的,那么就返回 false。获取更多信息,或者如果你有问题或关注,可以参阅accepts。req.get(field)返回指定的请求 HTTP 头部的域内容(不区分大小写)。Referrer和Referer的域内容可互换。req.get('Content-type'); // => "text/plain" req.get('content-type'); // => "text/plain" req.get('Something'); // => undefined其是req.header(field)的别名。req.is(type)如果进来的请求的Content-type头部域匹配参数type给定的MIME type,那么其返回true。否则返回false。// With Content-Type:text/html; charset=utf-8 req.is('html'); req.is('text/html'); req.is('text/*'); // => true // When Content-Type is application/json req.is('json'); req.is('application/json'); req.is('application/*'); // => true req.is('html'); // => false获取更多信息,或者如果你有问题或关注,可以参阅type-is。req.param(naem, [, defaultValue])过时的。可以在适合的情况下,使用req.params,req.body或者req.query。返回当前参数name的值。// ?name=tobi req.param('name'); // => "tobi" // POST name=tobi req.param('name'); // => "tobi" // /user/tobi for /user/:name req.param('name'); // => "tobi"按下面给出的顺序查找:req.paramsreq.bodyreq.query可选的,你可以指定一个defaultValue来设置一个默认值,如果这个参数在任何一个请求的对象中都不能找到。直接通过req.params,req.body,req.query取得应该更加的清晰-除非你确定每一个对象的输入。body-parser中间件必须加载,如果你使用req.param()。详细请看req.body。
2020年01月23日
155 阅读
0 评论
1 点赞
2020-01-03
限制服务调用次数
API 服务上线后,可能有被恶意调用或攻击的安全问题,尤其在不用鉴权就可以请求接口是更危险。目前攻击最常见的就是“短信轰炸机”,由于短信接口验证是App,网站检验用户手机号最真实的途径,使用短信验证码在提供便利的同时,也成了呗恶意攻击的对象,那么如何才能防止被恶意调用呢?
2020年01月03日
124 阅读
0 评论
1 点赞
1
2
3