From 89d6c5a3328bf705dca2d2de3fc4eac82800dab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=87=E5=A4=A9?= Date: Sat, 9 Apr 2022 20:26:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 18 ++++++ .prettierrc.yaml | 10 +++ .vscode/launch.json | 28 +++++++++ .vscodeignore | 10 +++ LICENSE | 21 +++++++ README.md | 25 ++++++++ index.js | 146 ++++++++++++++++++++++++++++++++++++++++++++ logo.png | Bin 0 -> 12438 bytes package.json | 57 +++++++++++++++++ 9 files changed, 315 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc.yaml create mode 100644 .vscode/launch.json create mode 100644 .vscodeignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 logo.png create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ec781c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +.DS_Store +.AppleDouble +.LSOverride + + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes +package-lock.json + +node_modules +.vscode-test +out.js +*.vsix +*.css \ No newline at end of file diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000..c0e277f --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,10 @@ +jsxBracketSameLine: true +jsxSingleQuote: true +semi: false +singleQuote: true +printWidth: 100 +useTabs: false +tabWidth: 2 +trailingComma: none +bracketSpacing: true +arrowParens: avoid \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6899e23 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +// A launch configuration that launches the extension inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ] + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/test/suite/index" + ] + } + ] +} diff --git a/.vscodeignore b/.vscodeignore new file mode 100644 index 0000000..1bd2951 --- /dev/null +++ b/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +.vscode-test/** +node_modules/** +test/** +package-lock.json +.travis.yml +.gitignore +index.js +test.js + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6142709 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# simple.http +> 🔥 简单的http服务器, 方便临时调试html。 + +[![Version](https://vsmarketplacebadge.apphb.com/version-short/yutent.simple.http.svg)](https://marketplace.visualstudio.com/items?itemName=yutent.simple.http) +[![Rating](https://vsmarketplacebadge.apphb.com/rating-short/yutent.simple.http.svg)](https://marketplace.visualstudio.com/items?itemName=yutent.simple.http) +[![Installs](https://vsmarketplacebadge.apphb.com/installs/yutent.simple.http.svg)](https://marketplace.visualstudio.com/items?itemName=yutent.simple.http) + +## 插件说明 +> 这插件只是应一个基友需求, 临时写的。如果你有其他的功能需求, 请在插件商店中`搜索其他插件`。 + + +## 插件使用 +> 在项目根目录中放置 `.httpserver`文件, 并配置好之后, 重启或重新加载窗口, 以启动服务。 + + + +## .httpserver 文件 +> 使用`.httpserver`来配置web服务器信息, 该文件应为一个json格式的。 + +```json +{ + "port": 23333, // 默认使用 23333 端口, 如果被使用了, 会向上查找可用端口 + "enabled": true // 这里配置为 true 才会启动web服务 +} +``` diff --git a/index.js b/index.js new file mode 100644 index 0000000..9cc77f5 --- /dev/null +++ b/index.js @@ -0,0 +1,146 @@ +/** + * + * @author yutent + * @date 2022/04/09 18:26:35 + */ + +const vsc = require('vscode') +const { join } = require('path') +const { parse } = require('url') +const http = require('http') +const fs = require('iofs') + +const std = vsc.window.createOutputChannel('http.server') +const decode = decodeURIComponent + +const MIME_TYPES = { + html: 'text/html;charset=utf-8', + txt: 'text/plain;charset=utf-8', + css: 'text/css;charset=utf-8', + xml: 'text/xml;charset=utf-8', + gif: 'image/gif', + jpg: 'image/jpeg', + webp: 'image/webp', + tiff: 'image/tiff', + png: 'image/png', + svg: 'image/svg+xml', + ico: 'image/x-icon', + bmp: 'image/x-ms-bmp', + js: 'application/javascript;charset=utf-8', + json: 'application/json;charset=utf-8', + mp3: 'audio/mpeg', + ogg: 'audio/ogg', + m4a: 'audio/x-m4a', + mp4: 'video/mp4', + webm: 'video/webm', + ttf: 'font/font-ttf', + woff: 'font/font-woff', + woff2: 'font/font-woff2', + other: 'application/octet-stream' +} + +MIME_TYPES.htm = MIME_TYPES.html +MIME_TYPES.jpeg = MIME_TYPES.jpg +MIME_TYPES.tif = MIME_TYPES.tiff + +let root +let enabled = false +let port = 23333 +let baseUrl = 'http://127.0.0.1:' + port + +std.out = function (...args) { + std.appendLine('[simple.http]: ' + args.join(' ')) +} + +function createServer() { + if (port > 65535) { + std.out('端口超出有效范围0-65535, 当前为: ' + port) + enabled = false + return + } + std.out(`尝试使用${port}端口...`) + http + .createServer(function (req, res) { + let pathname = parse(req.url) + .pathname.slice(1) + .replace(/[\/]+$/, '') + + pathname = decode(pathname) || 'index.html' + + let file = join(root, pathname) + let stat = fs.stat(file) + let ext = pathname.split('.').pop() + + res.setHeader('Access-Control-Allow-Origin', '*') + res.setHeader('Access-Control-Allow-Headers', '*') + res.setHeader('Cache-Control', 'no-store') + res.setHeader('X-Powered-By', 'VS Code simple.http') + + if (stat.isFile()) { + res.setHeader('Accept-Ranges', 'bytes') + res.setHeader('Content-Type', MIME_TYPES[ext] || MIME_TYPES.other) + res.setHeader('Content-Length', stat.size) + res.writeHead(200, 'OK') + fs.origin.createReadStream(file).pipe(res) + } else { + res.setHeader('Content-Type', MIME_TYPES.html) + res.setHeader('Content-Length', 0) + res.writeHead(404, 'Not Found') + res.end('') + } + }) + .listen(port) + .on('error', err => { + std.out(`${port}端口被占用~~~`) + port++ + createServer() + }) + .on('listening', _ => { + baseUrl = 'http://127.0.0.1:' + port + std.out('启动成功, 请访问', baseUrl) + }) +} + +function __init__() { + let folders = vsc.workspace.workspaceFolders + + if (folders && folders.length) { + root = folders[0].uri.fsPath + } + if (root) { + let file = join(root, '.httpserver') + // + if (fs.isfile(file)) { + let conf = JSON.parse(fs.cat(file).toString()) + if (conf.enabled) { + enabled = true + port = conf.port || 23333 + + createServer() + } else { + std.out('发现配置文件, 但服务为 关闭状态') + } + } + } +} + +function deactivate() {} + +exports.activate = function (ctx) { + __init__() + + let cmd = vsc.commands.registerCommand('HttpServer.open', _ => { + if (enabled) { + let editor = vsc.window.activeTextEditor + if (editor) { + console.log(editor) + + let pathname = editor.document.uri.fsPath.slice(root.length) + + vsc.commands.executeCommand('vscode.open', baseUrl + pathname) + } + } + }) + ctx.subscriptions.push(cmd) +} +exports.deactivate = deactivate diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c953a62949a1c01fc69eedb7ca249506bc8d50de GIT binary patch literal 12438 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