標準でHTTP範囲を解析する

プロジェクトの1つでは、HTTP範囲要求を解析して、ファイルを部分的にダウンロードするためのサポートを追加する必要がありました。 ネットワークにはさまざまな例がたくさんありますが、 RFC 2616の完全な実装は1つも見つかりませんでした。 1つのコードは複数の範囲が存在する可能性があることを考慮していませんでしたが、もう1つは標準がドキュメントサイズよりも大きい要求を許可するものであり、3つ目は標準が推奨するように構文的に正しい要求と到達不能な要求を区別していません。 そのため、実装を作成し、全員と共有することにしました。 catでのPHPの詳細と実装例。



標準状態では、範囲クエリは2つの部分で構成されます。範囲のディメンションと選択ルールのリストです。 RFC 2616で定義されている唯一の範囲ディメンションはバイトです。 また、同じRangeヘッダーには、一度にカンマで示される複数の範囲が存在する可能性があることに注意してください。



HTTPクライアントの範囲を取得するための2つのオプションがあります。



1つ目は、ドキュメントの本文の開始位置と終了位置の表示です。 最初の位置はゼロから始まります。 最後の位置は、リクエストの最初の位置以上でなければなりません。 それ以外の場合、標準によれば、この実装ヘッダーは無視する必要があります。 最後の位置が存在しないか、その値がドキュメントのサイズ以上である場合、最後の位置はドキュメントの現在のサイズをバイト単位で1減らしたものです。標準によれば、これはエラーではありません。



たとえば、サイズが10バイトのドキュメントの場合、 bytes=1-9



は、2番目からドキュメント本体の最後のバイトで終わる9バイトを要求します。



2番目は、ドキュメント本文の最後のNバイトの選択です。 ドキュメントがリクエストで指定されたサイズよりも小さい場合、ドキュメント全体が選択されます。 たとえば、 bytes=-2



は最後の2バイトを要求します。



すべての範囲の処理が完了した後、サーバーは、ゼロ以外のバイト数を含む範囲が少なくとも1つあるかどうかを判断する必要があります。 存在しない場合、サーバーはクライアント416(要求された範囲は満たされません)に応答する必要があります(そうでない場合は206(部分コンテンツ))。



条件を満たさない場合、実装は「条件付きで互換性がある」と見なされます(SHOULD)。 したがって、標準のすべての条件が満たされている場合、範囲要求の完全な処理を実現できます。



PHPの実装例:



 <?php namespace HTTP; /* * Copyright (c) 2012, aignospam@gmail.com * http://www.opensource.org/licenses/bsd-license.php */ /** * Parse HTTP Range header * http://tools.ietf.org/html/rfc2616#section-14.35 * return array of Range on success * false on syntactically invalid byte-range-spec * empty array on unsatisfiable bytes-range-set * @param int $entity_body_length * @param string range_header * @return array|bool */ function parse_range_request($entity_body_length, $range_header) { $range_list = array(); if ($entity_body_length == 0) { return $range_list; // mark unsatisfiable } // The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 // implementations MAY ignore ranges specified using other units. // Range unit "bytes" is case-insensitive if (preg_match('/^bytes=([^;]+)/i', $range_header, $match)) { $range_set = $match[1]; } else { return false; } // Wherever this construct is used, null elements are allowed, but do // not contribute to the count of elements present. That is, // "(element), , (element) " is permitted, but counts as only two elements. $range_spec_list = preg_split('/,/', $range_set, null, PREG_SPLIT_NO_EMPTY); foreach ($range_spec_list as $range_spec) { $range_spec = trim($range_spec); if (preg_match('/^(\d+)\-$/', $range_spec, $match)) { $first_byte_pos = $match[1]; if ($first_byte_pos > $entity_body_length) { continue; } $first_pos = $first_byte_pos; $last_pos = $entity_body_length - 1; } elseif (preg_match('/^(\d+)\-(\d+)$/', $range_spec, $match)) { $first_byte_pos = $match[1]; $last_byte_pos = $match[2]; // If the last-byte-pos value is present, it MUST be greater than or // equal to the first-byte-pos in that byte-range-spec if ($last_byte_pos < $first_byte_pos) { return false; } $first_pos = $first_byte_pos; $last_pos = min($entity_body_length - 1, $last_byte_pos); } elseif (preg_match('/^\-(\d+)$/', $range_spec, $match)) { $suffix_length = $match[1]; if ($suffix_length == 0) { continue; } $first_pos = $entity_body_length - min($entity_body_length, $suffix_length); $last_pos = $entity_body_length - 1; } else { return false; } $range_list[] = new Range($first_pos, $last_pos); } return $range_list; } class Range { private $_first_pos; private $_last_pos; public function __construct($first_pos, $last_pos) { $this->_first_pos = $first_pos; $this->_last_pos = $last_pos; } public function get_first_pos() { return $this->_first_pos; } public function get_last_pos() { return $this->_last_pos; } } ?>
      
      






All Articles