欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

WebSocket Node構(gòu)建HTTP隧道實(shí)現(xiàn)實(shí)例

 更新時(shí)間:2023年11月24日 10:05:10   作者:月弦笙音  
這篇文章主要為大家介紹了WebSocket Node構(gòu)建HTTP隧道實(shí)現(xiàn)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

當(dāng)我們開(kāi)發(fā)一些與第三方服務(wù)集成的應(yīng)用程序時(shí),我們需要使我們的本地開(kāi)發(fā)服務(wù)器暴露在網(wǎng)上。為此,我們需要為本地服務(wù)器提供一個(gè) HTTP 隧道。HTTP 隧道如何工作?嘎嘎嘎,看下面!????

為什么我們需要部署自己的 HTTP 隧道服務(wù)?

HTTP隧道有很多很?的在線服務(wù)。??,我們可以用來(lái)獲得付費(fèi)的固定公共域來(lái)連接你的本地服務(wù)器。它還有一個(gè)免費(fèi)包。但是對(duì)于免費(fèi)套餐??,你無(wú)法獲得固定域。重新啟動(dòng)客戶端后,你將獲得一個(gè)新的隨機(jī)域。當(dāng)你需要在第三方服務(wù)中保存域時(shí),這很不方便。

要獲得固定域,我們可以在自己的服務(wù)器中部署HTTP隧道。 還提供用于服務(wù)器端部署的開(kāi)源版本。但它是舊的 1.x 版本,不建議在生產(chǎn)環(huán)境中部署,存在一些嚴(yán)重的可靠性問(wèn)題。

使用我們自己的服務(wù)器,它還可以確保數(shù)據(jù)安全。?

關(guān)于精簡(jiǎn)版 HTTP 隧道簡(jiǎn)介

Lite HTTP 隧道是我最近為自托管 HTTP 隧道服務(wù)構(gòu)建的。你可以使用 Github 存儲(chǔ)庫(kù)中的按鈕部署它,以快速獲得免費(fèi)的固定 Heroku 域。Heroku

它是基于并且只有幾個(gè)代碼構(gòu)建的。它使用 WebSocket 將 HTTP/HTTPS 請(qǐng)求從公共服務(wù)器流式傳輸?shù)奖镜胤?wù)器。Express.js`Socket.io`

如何實(shí)現(xiàn)它

步驟 1:在服務(wù)器和客戶端之間建立 WebSocket 連接

支持服務(wù)器端的WebSocket連接:socket.io

const http = require('http');
const express = require('express');
const { Server } = require('socket.io');
const app = express();
const httpServer = http.createServer(app);
const io = new Server(httpServer);
let connectedSocket = null;
io.on('connection', (socket) => {
  console.log('client connected');
  connectedSocket = socket;
  const onMessage = (message) => {
    if (message === 'ping') {
      socket.send('pong');
    }
  }
  const onDisconnect = (reason) => {
    console.log('client disconnected: ', reason);
    connectedSocket = null;
    socket.off('message', onMessage);
    socket.off('error', onError);
  };
  const onError = (e) => {
    connectedSocket = null;
    socket.off('message', onMessage);
    socket.off('disconnect', onDisconnect);
  };
  socket.on('message', onMessage);
  socket.once('disconnect', onDisconnect);
  socket.once('error', onError);
});
httpServer.listen(process.env.PORT);

?在客戶端連接 WebSocket:

const { io } = require('socket.io-client');
let socket = null;
function initClient(options) {
  socket = io(options.server, {
    transports: ["websocket"],
    auth: {
      token: options.jwtToken,
    },
  });
  socket.on('connect', () => {
    if (socket.connected) {
      console.log('client connect to server successfully');
    }
  });
  socket.on('connect_error', (e) => {
    console.log('connect error', e && e.message);
  });
  socket.on('disconnect', () => {
    console.log('client disconnected');
  });
}

步驟 2:使用 JWT 令牌保護(hù) Websocket 連接

?在服務(wù)器端,我們使用 socket.io 中間件來(lái)拒絕無(wú)效連接:

const jwt = require('jsonwebtoken');
io.use((socket, next) => {
  if (connectedSocket) {
    return next(new Error('Connected error'));
  }
  if (!socket.handshake.auth || !socket.handshake.auth.token){
    next(new Error('Authentication error'));
  }
  jwt.verify(socket.handshake.auth.token, process.env.SECRET_KEY, function(err, decoded) {
    if (err) {
      return next(new Error('Authentication error'));
    }
    if (decoded.token !== process.env.VERIFY_TOKEN) {
      return next(new Error('Authentication error'));
    }
    next();
  });  
});

步驟 3:將請(qǐng)求從服務(wù)器流式傳輸?shù)娇蛻舳?/h3>

?我們實(shí)現(xiàn)一個(gè)可寫(xiě)流來(lái)將請(qǐng)求數(shù)據(jù)發(fā)送到隧道客戶端:

const { Writable } = require('stream');
class SocketRequest extends Writable {
  constructor({ socket, requestId, request }) {
    super();
    this._socket = socket;
    this._requestId = requestId;
    this._socket.emit('request', requestId, request);
  }
  _write(chunk, encoding, callback) {
    this._socket.emit('request-pipe', this._requestId, chunk);
    this._socket.conn.once('drain', () => {
      callback();
    });
  }
  _writev(chunks, callback) {
    this._socket.emit('request-pipes', this._requestId, chunks);
    this._socket.conn.once('drain', () => {
      callback();
    });
  }
  _final(callback) {
    this._socket.emit('request-pipe-end', this._requestId);
    this._socket.conn.once('drain', () => {
      callback();
    });
  }
  _destroy(e, callback) {
    if (e) {
      this._socket.emit('request-pipe-error', this._requestId, e && e.message);
      this._socket.conn.once('drain', () => {
        callback();
      });
      return;
    }
    callback();
  }
}
app.use('/', (req, res) => {
  if (!connectedSocket) {
    res.status(404);
    res.send('Not Found');
    return;
  }
  const requestId = uuidV4();
  const socketRequest = new SocketRequest({
    socket: connectedSocket,
    requestId,
    request: {
      method: req.method,
      headers: { ...req.headers },
      path: req.url,
    },
  });
  const onReqError = (e) => {
    socketRequest.destroy(new Error(e || 'Aborted'));
  }
  req.once('aborted', onReqError);
  req.once('error', onReqError);
  req.pipe(socketRequest);
  req.once('finish', () => {
    req.off('aborted', onReqError);
    req.off('error', onReqError);
  });
  // ...
});

?實(shí)現(xiàn)可讀的流以在客戶端獲取請(qǐng)求數(shù)據(jù):

const stream = require('stream');
class SocketRequest extends stream.Readable {
  constructor({ socket, requestId }) {
    super();
    this._socket = socket;
    this._requestId = requestId;
    const onRequestPipe = (requestId, data) => {
      if (this._requestId === requestId) {
        this.push(data);
      }
    };
    const onRequestPipes = (requestId, data) => {
      if (this._requestId === requestId) {
        data.forEach((chunk) => {
          this.push(chunk);
        });
      }
    };
    const onRequestPipeError = (requestId, error) => {
      if (this._requestId === requestId) {
        this._socket.off('request-pipe', onRequestPipe);
        this._socket.off('request-pipes', onRequestPipes);
        this._socket.off('request-pipe-error', onRequestPipeError);
        this._socket.off('request-pipe-end', onRequestPipeEnd);
        this.destroy(new Error(error));
      }
    };
    const onRequestPipeEnd = (requestId, data) => {
      if (this._requestId === requestId) {
        this._socket.off('request-pipe', onRequestPipe);
        this._socket.off('request-pipes', onRequestPipes);
        this._socket.off('request-pipe-error', onRequestPipeError);
        this._socket.off('request-pipe-end', onRequestPipeEnd);
        if (data) {
          this.push(data);
        }
        this.push(null);
      }
    };
    this._socket.on('request-pipe', onRequestPipe);
    this._socket.on('request-pipes', onRequestPipes);
    this._socket.on('request-pipe-error', onRequestPipeError);
    this._socket.on('request-pipe-end', onRequestPipeEnd);
  }
  _read() {}
}
socket.on('request', (requestId, request) => {
  console.log(`${request.method}: `, request.path);
  request.port = options.port;
  request.hostname = options.host;
  const socketRequest = new SocketRequest({
    requestId,
    socket: socket,
  });
  const localReq = http.request(request);
  socketRequest.pipe(localReq);
  const onSocketRequestError = (e) => {
    socketRequest.off('end', onSocketRequestEnd);
    localReq.destroy(e);
  };
  const onSocketRequestEnd = () => {
    socketRequest.off('error', onSocketRequestError);
  };
  socketRequest.once('error', onSocketRequestError);
  socketRequest.once('end', onSocketRequestEnd);
  // ...
});

步驟 4:將響應(yīng)從客戶端流式傳輸?shù)椒?wù)器

?實(shí)現(xiàn)可寫(xiě)流以將響應(yīng)數(shù)據(jù)發(fā)送到隧道服務(wù)器:

const stream = require('stream');
class SocketResponse extends stream.Writable {
  constructor({ socket, responseId }) {
    super();
    this._socket = socket;
    this._responseId = responseId;
  }
  _write(chunk, encoding, callback) {
    this._socket.emit('response-pipe', this._responseId, chunk);
    this._socket.io.engine.once('drain', () => {
      callback();
    });
  }
  _writev(chunks, callback) {
    this._socket.emit('response-pipes', this._responseId, chunks);
    this._socket.io.engine.once('drain', () => {
      callback();
    });
  }
  _final(callback) {
    this._socket.emit('response-pipe-end', this._responseId);
    this._socket.io.engine.once('drain', () => {
      callback();
    });
  }
  _destroy(e, callback) {
    if (e) {
      this._socket.emit('response-pipe-error', this._responseId, e && e.message);
      this._socket.io.engine.once('drain', () => {
        callback();
      });
      return;
    }
    callback();
  }
  writeHead(statusCode, statusMessage, headers) {
    this._socket.emit('response', this._responseId, {
      statusCode,
      statusMessage,
      headers,
    });
  }
}
socket.on('request', (requestId, request) => {
    // ...stream request and send request to local server...
    const onLocalResponse = (localRes) => {
      localReq.off('error', onLocalError);
      const socketResponse = new SocketResponse({
        responseId: requestId,
        socket: socket,
      });
      socketResponse.writeHead(
        localRes.statusCode,
        localRes.statusMessage,
        localRes.headers
      );
      localRes.pipe(socketResponse);
    };
    const onLocalError = (error) => {
      console.log(error);
      localReq.off('response', onLocalResponse);
      socket.emit('request-error', requestId, error && error.message);
      socketRequest.destroy(error);
    };
    localReq.once('error', onLocalError);
    localReq.once('response', onLocalResponse);
  });

?實(shí)現(xiàn)可讀流以在隧道服務(wù)器中獲取響應(yīng)數(shù)據(jù):

class SocketResponse extends Readable {
  constructor({ socket, responseId }) {
    super();
    this._socket = socket;
    this._responseId = responseId;
    const onResponse = (responseId, data) => {
      if (this._responseId === responseId) {
        this._socket.off('response', onResponse);
        this._socket.off('request-error', onRequestError);
        this.emit('response', data.statusCode, data.statusMessage, data.headers);
      }
    }
    const onResponsePipe = (responseId, data) => {
      if (this._responseId === responseId) {
        this.push(data);
      }
    };
    const onResponsePipes = (responseId, data) => {
      if (this._responseId === responseId) {
        data.forEach((chunk) => {
          this.push(chunk);
        });
      }
    };
    const onResponsePipeError = (responseId, error) => {
      if (this._responseId !== responseId) {
        return;
      }
      this._socket.off('response-pipe', onResponsePipe);
      this._socket.off('response-pipes', onResponsePipes);
      this._socket.off('response-pipe-error', onResponsePipeError);
      this._socket.off('response-pipe-end', onResponsePipeEnd);
      this.destroy(new Error(error));
    };
    const onResponsePipeEnd = (responseId, data) => {
      if (this._responseId !== responseId) {
        return;
      }
      if (data) {
        this.push(data);
      }
      this._socket.off('response-pipe', onResponsePipe);
      this._socket.off('response-pipes', onResponsePipes);
      this._socket.off('response-pipe-error', onResponsePipeError);
      this._socket.off('response-pipe-end', onResponsePipeEnd);
      this.push(null);
    };
    const onRequestError = (requestId, error) => {
      if (requestId === this._responseId) {
        this._socket.off('request-error', onRequestError);
        this._socket.off('response', onResponse);
        this._socket.off('response-pipe', onResponsePipe);
        this._socket.off('response-pipes', onResponsePipes);
        this._socket.off('response-pipe-error', onResponsePipeError);
        this._socket.off('response-pipe-end', onResponsePipeEnd);
        this.emit('requestError', error);
      }
    };
    this._socket.on('response', onResponse);
    this._socket.on('response-pipe', onResponsePipe);
    this._socket.on('response-pipes', onResponsePipes);
    this._socket.on('response-pipe-error', onResponsePipeError);
    this._socket.on('response-pipe-end', onResponsePipeEnd);
    this._socket.on('request-error', onRequestError);
  }
  _read(size) {}
}
app.use('/', (req, res) => {
  // ... stream request to tunnel client
  const onResponse = (statusCode, statusMessage, headers) => {
    socketRequest.off('requestError', onRequestError)
    res.writeHead(statusCode, statusMessage, headers);
  };
  socketResponse.once('requestError', onRequestError)
  socketResponse.once('response', onResponse);
  socketResponse.pipe(res);
  const onSocketError = () => {
    res.end(500);
  };
  socketResponse.once('error', onSocketError);
  connectedSocket.once('close', onSocketError)
  res.once('close', () => {
    connectedSocket.off('close', onSocketError);
    socketResponse.off('error', onSocketError);
  });
});

??完成所有步驟后,我們支持將 HTTP 請(qǐng)求流式傳輸?shù)奖镜赜?jì)算機(jī),并將響應(yīng)從本地服務(wù)器發(fā)送到原始請(qǐng)求。它是一個(gè)精簡(jiǎn)的解決方案,但它穩(wěn)定且易于在任何環(huán)境中部署。Node.js

更多

如果你只想查找具有免費(fèi)固定域的HTTP隧道服務(wù),則可以嘗試在Github自述文件中Lite HTTP Tunnel項(xiàng)目部署到with中。希望小伙伴們能從這篇文章中學(xué)到一些東西??。Heroku`Heroku deploy button`

以上就是WebSocket Node構(gòu)建HTTP隧道實(shí)現(xiàn)實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于WebSocket Node構(gòu)建HTTP的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論