LoginSignup
0
0

クライアントのpako.inflateがエラーになっちゃう対応

Posted at

TL;DR

サーバでpako.deflateして送信したデータが、クライアントでpako.inflateしようとすると「incorrect header check」エラー、または「undefined」となる問題の対策

■サーバ側
サーバでpako.deflateは "raw:true" しろ
pako.deflate(JSON.stringify(data), { encoding: "utf-8", raw: true });

■クライアント側
base64をatobで変換した文字列をカンマ区切りでUint8Arrayを作成してpako.inflateに渡せ
inflateオプションは{ to: "string", raw: true }としろ

背景

サーバでNode.jsを使いSSE方式で所定のクライアント群にデータを送信する
送信するデータはJSONでpako.deflateで圧縮し、BASE64でクライアントに送信する

Server JSON → pako.deflate → BASE64

Client BASE64 → :thinking:pako.inflate → JSON.Parse

クライアントではBASE64をエンコードし、Pako.inflateで展開し元のJSON文字列を取得したい。
しかしpako.inflate展開時にエラーでハマった

対策済みコード例

  • サーバ側「Worker」が動作しデータを取得
    取得した値をdeflateで圧縮
    1秒周期でクライアントに送信
Server.js
const pako = require("pako");
const express = require('express');
const app = express();

const { Worker } = require("worker_threads");
// ワーカースレッドを作成 - データ取得処理
const worker = new Worker("./worker.js");

// 内部バッファの初期化
let internalBuffer = "";
// 接続しているクライアントのリスト
let clients = [];

// ワーカースレッドからのデータを受信
worker.on('message', (data) => {
	// 受信したデータを変数に格納
	internalBuffer = pako.deflate(JSON.stringify(data), { encoding: "utf-8", raw: true });
});

// 初回のリクエストを受信するエンドポイント
app.get('/api/request', (req, res) => {
  res.header("Access-Control-Allow-Origin", "http://localhost:3000");
	// SSEの処理
	res.setHeader("Content-Type", "text/event-stream");
	res.setHeader("Cache-Control", "no-cache");
	res.setHeader("Connection", "keep-alive");
	res.setHeader("X-Accel-Buffering", "no");

	// クライアントをリストに追加
	clients.push(res);
	req.on("close", () => {
     	// 接続が閉じられた場合、クライアントをリストから削除
		clients = clients.filter((client) => client !== res);
	});
});

// 毎秒すべてのクライアントにデータをプッシュ
setInterval(() => {
	clients.forEach(client => {
		client.write(`data:application/octet-stream;base64,${btoa(internalBuffer)}\n\n`);
	});
}, 1000);

// サーバを起動
const PORT = process.env.PORT || 4000; 
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
  • クライアント側 React
    取得した値をpako.inflateで展開、JSONを取得
App.tsx
import React, { useEffect, useState } from "react";
import "./App.css";
import axios from "axios";
import pako from "pako";

const MainComponent: React.FC = () => {
  const groupServerAddress = "http://localhost:4000/api/request"
  useEffect(() => {
      const eventSource = new EventSource(groupServerAddress);
      // SSEからデータを受け取る処理
      eventSource.onmessage = async (event: MessageEvent) => {
        let eventData = event.data;
        // データが圧縮されているかどうかをチェック
        if (eventData.trim().startsWith("application/octet-stream;base64,")) {
          // Base64エンコードされたデータを取得
          eventData = eventData.substring("application/octet-stream;base64,".length);
          try {
				// Base64デコード
				const decodedData = atob(eventData.trim());
                const sourceArray = new Uint8Array(decodedData.split(",").map(Number)); 
               
//inflateで展開 - ここでエラーが出ていた
                const decompressedData = pako.inflate(sourceArray, { to: "string", raw: true });

				console.log("受信したデータ:", JSON.parse(decompressedData));
            } 
            catch (error) {
                console.log(error); 
            }
        } 
      };
      return () => {
        eventSource.close();
      };
    }
  }, [groupServerAddress]);
  return null;
};

function App() {
	return (
		<div className="App">
			<MainComponent /> 
		</div>
	);
}
export default App;

解説

・クライアント React側にてサーバからmessageを受信後
const decodedData = atob(eventData.trim());
でBASE64を変換し、そこからUint8Arrayを作っているが、これは文字列に対する操作になっており

たとえばatob後のデータが以下のケース場合
例)"237,146,77,78,195,78…"
eventData[0] = "2"
eventData[1] = "3"
eventData[2] = "7"
eventData[3] = ","

を元にしたByteArrayが生成される。
これをinflateすると「Incorrect header check」となる

bad_code.js
//NGコード例
const decodedData = atob(eventData.trim());
const byteNumbers = new Array(decodedData.length);
for (let i = 0; i < decodedData.length; i++) {
 	byteNumbers[i] = decodedData.charCodeAt(i);
 }
// バイト値の配列をUint8Arrayに変換
const byteArray = new Uint8Array(byteNumbers);

//Incorrect header checkとなる
const decompressedData = pako.inflate(sourceArray, { to: "string", raw: true });

元データの通り
eventData[0] = "237"
eventData[1] = "146"
eventData[2] = "77"

としたUint8Arrayをinflateすれば解決する。

inflateのエラー

・エラー「Incorrect header check」は、データの圧縮形式に問題があることを示す
・エラーは出ないが、結果が「undefined」は渡されるデータが誤っている

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0