- PR -

XML WebService でのデータ圧縮で困ってます

投稿者投稿内容
ドルビー
常連さん
会議室デビュー日: 2006/07/11
投稿数: 21
投稿日時: 2007-02-21 09:22
VisualStudio 2005 で、XML WebService のプログラムを書いていますが、
クライアント〜サーバ間の通信を、SoapExtension を使って、
SOAPメッセージ全体を、GZipStream を使って圧縮・解凍をする、
という機能を実装しようと考えています。

実際に、サンプル程度に実装を行なってみましたが、
AfterSerialize では、GZipでの圧縮が確認されましたが、
BeforeDeserializeでは、ChainStream で引数でわたってくる
ストリームの内容が 0 バイトとなってしまいます。
結果的に、解凍もできない為、Bad Request 例外がスローされます。

public override System.IO.Stream ChainStream(System.IO.Stream stream)
{
  _oldStream = stream;
  _newStream = new MemoryStream();
  return _newStream;
}

public override void ProcessMessage(SoapMessage message)
{
  switch (message.Stage)
  {
    case SoapMessageStage.BeforeSerialize:
      break;

    case SoapMessageStage.AfterSerialize:
      // 圧縮します
      this.CompressSoapMessage(message);
      break;

    case SoapMessageStage.BeforeDeserialize:
      // 解凍します
      this.DeCompressSoapMessage(message);
      break;

    case SoapMessageStage.AfterDeserialize:
      break;

    default:
      throw new Exception("invalid stage");
  }
}


圧縮については、_newStream の内容を圧縮した結果を、
_oldStream に書いて、最後に _oldStream.Flush() を呼んでいます。

解凍については、_oldStream の内容を解凍した結果を、
_newStream に書いて、最後に _newStream.Flush() を呼んでいます。


何が悪いのかが調べてはみたのですが、わからない状態です。
アドバイスお願いします。
masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2007-02-21 12:56
引用:


圧縮については、_newStream の内容を圧縮した結果を、
_oldStream に書いて、最後に _oldStream.Flush() を呼んでいます。




この時点で _outStream の長さはゼロではないのですよね?
Position はいくつをさしているでしょうか。
おそらくゼロではなく、ストリームの末尾をさしていると思います。
Length にゼロをセットするか、Seek で先頭を指すようにしてから
戻り値としていますでしょうか。

WebService 内でストリームをどのように扱われているかによりますが、
データの先頭を指している状態であるという前提で処理が行われているとすると、
データなしと判断されるかもしれないですね。



[ メッセージ編集済み 編集者: masa 編集日時 2007-02-21 12:58 ]

[ メッセージ編集済み 編集者: masa 編集日時 2007-02-21 12:59 ]
ドルビー
常連さん
会議室デビュー日: 2006/07/11
投稿数: 21
投稿日時: 2007-02-21 15:32
masa さん、こんにちは。

ご指摘の点についてですが、_outStream は、
<System.Web.Services.Protocols.SoapExtensionStream>となっています。
このストリームは CanSeek = false ということですので、
クイック ウォッチを確認すると例外がスローされている常態です。

そこで、一時的に _oldStream の変わりに、MemoryStream を生成して、
同じ処理を実行してみたところ、圧縮データが書き込まれていることが
判明しました。


余談ですが、

解凍のステップに入ると _oldStream は <System.Net.SyncMemoryStream> となっています。
このときはシーク可能な状態となっています。
masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2007-02-21 15:58
圧縮をしているのは ChainStream メソッドですか?
圧縮を行っている部分のコードが出せるのであれば、
糸口が見つかるかもしれません。

私は .NET Remoting で同じような処理を実装しましたが、
引数で受け取ったネットワークストリームを以下の手順で圧縮し、
圧縮後のストリームを返しています。

・書き込み用のメモリストリームを作成
・そのメモリストリームをバッファとした圧縮ストリームを作成
・ネットワークストリームを読み込み、圧縮ストリームに書き込み
・圧縮ストリームをFlush
・バッファとしていたメモリストリームを返す

圧縮ストリームは .NET2.0 で追加されたものを使用しています。

今、手元にソースがないので若干異なる部分があるかもしれません。
ドルビー
常連さん
会議室デビュー日: 2006/07/11
投稿数: 21
投稿日時: 2007-02-21 17:14
masa さん、迅速な回答ありがとうございます。

圧縮を行なっているのは、CompressSoapMessage()メソッドです。
AfterSerialize で圧縮をして、BeforeDeserialize で解凍しています。
ChainStream で予め、ストリームを受け取っています。

圧縮の処理ですが、クラス化していますが抜粋ということで提供します。
以下は、圧縮・解凍を行なうクラスで、予めコンストラクタでストリームを受け取り、
_stream に格納しています。
以下は、そのクラスの圧縮と解凍の各メソッドです。
オプションとして、ストリームを読み込むときのバッファサイズを指定できるようにしています。
結合テストレベルでは正常に圧縮と解凍が行なわれていることは確認できています。

/// <summary>
/// 処理対象のストリームを圧縮し、指定されたストリームに対して出力します。
/// </summary>
/// <param name="stream">出力ストリーム。</param>
/// <param name="bufferSize">処理を行なうバッファサイズ。</param>
/// <returns>圧縮に成功したときは true。</returns>
public bool Compress(Stream stream, int bufferSize)
{
  if (stream == null)
    throw new ArgumentNullException("stream");

  if (bufferSize <= 0)
    throw new ArgumentOutOfRangeException("バッファサイズは 1 バイト以上が必要です。");

  // 処理対象のストリームが読み込みできないときは処理をしません。
  if (!_stream.CanSeek) return false;
  // 出力先のストリームが書き込めないときは処理をしません。
  if (!stream.CanWrite) return false;

  // 読込み開始位置を先頭にします
  if (_stream.CanSeek)
    _stream.Position = 0;

  // 出力ストリームを初期化します
  if (stream.CanSeek)
  {
    stream.SetLength(0);
    stream.Flush();
  }

  // GZip 圧縮を行ないます。
  int inputSize = 0;
  byte[] buffer = new byte[bufferSize];

  // GZipStream を生成する
  GZipStream zipStream = new GZipStream(stream, CompressionMode.Compress);

  // 入力ストリームを読み込んで GZipStream に書き込む
  while ((inputSize = _stream.Read(buffer, 0, bufferSize)) > 0)
  {
    zipStream.Write(buffer, 0, inputSize);
  }

  // zipStream をフラッシュします
  zipStream.Flush();

  return true;
}

/// <summary>
/// 圧縮された処理対象のストリームを解凍し、指定されたストリームに対して出力します。
/// </summary>
/// <param name="stream">出力ストリーム。</param>
/// <param name="bufferSize">処理を行なうバッファサイズ。</param>
/// <returns>解凍に成功したときは true。</returns>
public bool Expand(Stream stream, int bufferSize)
{
  if (stream == null)
    throw new ArgumentNullException("stream");

  if (bufferSize <= 0)
    throw new ArgumentOutOfRangeException("バッファサイズは 1 バイト以上が必要です。");

  // 処理対象のストリームが読み込みできないときは処理をしません。
  if (!_stream.CanSeek) return false;
  // 出力先のストリームが書き込めないときは処理をしません。
  if (!stream.CanWrite) return false;

  // 読込開始位置を先頭にします
  if (_stream.CanSeek)
    _stream.Position = 0;

  // 出力ストリームを初期化します。
  if (stream.CanSeek)
  {
    stream.SetLength(0);
    stream.Flush();
  }

  int inputSize = 0;
  byte[] buffer = new byte[bufferSize];


  // GZipStream を解凍モードで生成します
  GZipStream zipStream = new GZipStream(_stream, CompressionMode.Decompress);

  // GZipStream を読み込んで出力ストリームに書き込みます
  while ((inputSize = zipStream.Read(buffer, 0, bufferSize)) > 0)
  {
    stream.Write(buffer, 0, inputSize);
    stream.Flush();
  }

  return true;
}
ドルビー
常連さん
会議室デビュー日: 2006/07/11
投稿数: 21
投稿日時: 2007-02-21 17:22
masa さん、度々申し訳ございません。

先ほど投稿したソースで再度、実行してみたところ、
BeforeDeserialize の段階できちんと _oldStream に情報が入ってきている
ことがわかりました。

っが、しかし今度は GZipStream の解凍ができません。
具体的には例外が発生しておりまして、

System.Xml.XmlException: '' (16 進数値 0x1F) は無効な文字です。

というメッセージが出力されています。
入力されたストリームを圧縮する、というのは邪道なのでしょうか???
masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2007-02-21 19:17

http://www.ogis-ri.co.jp/otc/hiroba/technical/AxisDotNet/index.html

このページと同じようなことをしようとしているわけですよね?
(この例では相手がJavaですが)

この例では圧縮ストリームを作成した後で、
さらにもう一つストリームを作成してデータを移しています。
直接圧縮ストリームを渡すとうまく読み取られないのかもしれません。

圧縮や暗号化ストリームにはいろいろありますが実装に違いがあるようです。

・Close すると内部のストリームまで Close するもの、しないもの
・シークできるもの、できないもの

など

私も .NET Remoting で「データがない」といった現象を経験しています。
まだ検証中の段階ですが、私も圧縮ストリームから別のストリームに移して返しています。確か、そのまま圧縮ストリームを渡すと「データなし」となったような。

できるだけ使用するストリームの数を減らしたいんですよね。でも難しい・・。

masa
大ベテラン
会議室デビュー日: 2004/10/28
投稿数: 161
投稿日時: 2007-02-21 22:00
検証中のコードを確認してみました。

System.IO.Compression.DeflateStream を使って圧縮しています。

1.バッファ兼戻り値とする MemoryStream を生成。
2.その MemoryStream を渡して DeflateStream を圧縮モードで生成。
3.ネットワークストリームのデータを DeflateStream に書き込み。
4.DeflateStream を Flush
5.DeflateStream を Close
6.MemoriStream を戻り値として返す。

ポイントは5です。
Close を行わない場合、解凍時に解析エラーがでました。
Close をしてもバッファとして使用していた MemoryStream は閉じられないようです。

スキルアップ/キャリアアップ(JOB@IT)