SIP メッセージを適切に解析してください。ブランチ ID だけが必要になる可能性はほとんどなく、疑似呼び出し ID 以外のトランザクションに関する他の情報が必要になることはほぼ確実です。SIP メッセージは、他のいくつかのプロトコル (HTTP を含む ;-) で使用される標準化されたメッセージ形式に従っており、このメッセージ形式を解析するために設計されたライブラリがいくつかあります。
これがいかに比較的単純で、かなり強力であるかを示すために、まず、私が以前に書いた RFC822 メッセージ パーサー クラスを見てみましょう (これらは最近改善および更新されています)。これらは電子メールの解析に使用できます。また、これらから拡張されたいくつかの単純な HTTP メッセージ パーサー クラスもあります。
<?php
/**
* Class representing the basic RFC822 message format
*
* @author Chris Wright
* @version 1.1
*/
class RFC822Message
{
/**
* @var array Collection of headers from the message
*/
protected $headers = array();
/**
* @var string The message body
*/
protected $body;
/**
* Constructor
*
* @param array $headers Collection of headers from the message
* @param string $body The message body
*/
public function __construct($headers, $body)
{
$this->headers = $headers;
$this->body = $body;
}
/**
* Get the value of a header from the message
*
* @param string $name The name of the header
*
* @return array The value(s) of the header from the request
*/
public function getHeader($name)
{
$name = strtolower(trim($name));
return isset($this->headers[$name]) ? $this->headers[$name] : null;
}
/**
* Get the message body
*
* @return string The message body
*/
public function getBody()
{
return $this->body;
}
}
/**
* Factory which makes RFC822 message objects
*
* @author Chris Wright
* @version 1.1
*/
class RFC822MessageFactory
{
/**
* Create a new RFC822 message object
*
* @param array $headers The request headers
* @param string $body The request body
*/
public function create($headers, $body)
{
return new RFC822Message($headers, $body);
}
}
/**
* Parser which creates RFC822 message objects from strings
*
* @author Chris Wright
* @version 1.2
*/
class RFC822MessageParser
{
/**
* @var RFC822MessageFactory Factory which makes RFC822 message objects
*/
protected $messageFactory;
/**
* Constructor
*
* @param RFC822MessageFactory $messageFactory Factory which makes RFC822 message objects
*/
public function __construct(RFC822MessageFactory $messageFactory)
{
$this->messageFactory = $messageFactory;
}
/**
* Split a message into head and body sections
*
* @param string $message The message string
*
* @return array Head at index 0, body at index 1
*/
protected function splitHeadFromBody($message)
{
$parts = preg_split('/\r?\n\r?\n/', ltrim($message), 2);
return array(
$parts[0],
isset($parts[1]) ? $parts[1] : null
);
}
/**
* Parse the header section into a normalized array
*
* @param string $head The message head section
*
* @return array The parsed headers
*/
protected function parseHeaders($head)
{
$expr =
'!
^
([^()<>@,;:\\"/[\]?={} \t]+) # Header name
[ \t]*:[ \t]*
(
(?:
(?: # First line of value
(?:"(?:[^"\\\\]|\\\\.)*"|\S+) # Quoted string or unquoted token
[ \t]* # LWS
)*
(?: # Folded lines
\r?\n
[ \t]+ # ...must begin with LWS
(?:
(?:"(?:[^"\\\\]|\\\\.)*"|\S+) # ...followed by quoted string or unquoted tokens
[ \t]* # ...and maybe some more LWS
)*
)*
)?
)
\r?$
!smx';
preg_match_all($expr, $head, $matches);
$headers = array();
for ($i = 0; isset($matches[0][$i]); $i++) {
$name = strtolower($matches[1][$i]);
if (!isset($headers[$name])) {
$headers[$name] = array();
}
$value = preg_replace('/\s+("(?:[^"\\\\]|\\\\.)*"|\S+)/s', ' $1', $matches[2][$i]);
$headers[$name][] = $value;
}
return $headers;
}
/**
* Create a message object from a string
*
* @param string $message The message string
*
* @return RFC822Message The parsed message object
*/
public function parseMessage($message)
{
list($head, $body) = $this->splitHeadFromBody($message);
$headers = $this->parseHeaders($head);
return $this->requestFactory->create($headers, $body);
}
}
ヘッダーを解析するための恐ろしい正規表現を無視すれば、特に恐ろしいことはありません:-P - 真剣に、これらのクラスは、RFC822 形式のメッセージの基礎である電子メールのヘッダー セクションを解析するために変更せずに使用できます。
SIP 自体は HTTP でモデル化されているため、HTTP メッセージ解析クラスにいくつかのかなり単純な変更を加えることで、それらを SIP に簡単に適応させることができます。それらを見てみましょう-これらのクラスで、(多かれ少なかれ)検索をHTTP
行い、次のように置き換えましたSIP
:
<?php
/**
* Abstract class representing a SIP message
*
* @author Chris Wright
* @version 1.0
*/
abstract class SIPMessage extends RFC822Message
{
/**
* @var string The message protocol version
*/
protected $version;
/**
* Constructor
*
* @param array $headers Collection of headers from the message
* @param string $body The message body
* @param string $version The message protocol version
*/
public function __construct($headers, $body, $version)
{
parent::__construct($headers, $body);
$this->version = $version;
}
/**
* Get the message protocol version
*
* @return string The message protocol version
*/
public function getVersion()
{
return $this->version;
}
}
/**
* Class representing a SIP request message
*
* @author Chris Wright
* @version 1.0
*/
class SIPRequest extends SIPMessage
{
/**
* @var string The request method
*/
private $method;
/**
* @var string The request URI
*/
private $uri;
/**
* Constructor
*
* @param array $headers The request headers
* @param string $body The request body
* @param string $version The request protocol version
* @param string $method The request method
* @param string $uri The request URI
*/
public function __construct($headers, $body, $version, $method, $uri)
{
parent::__construct($headers, $body, $version);
$this->method = $method;
$this->uri = $uri;
}
/**
* Get the request method
*
* @return string The request method
*/
public function getMethod()
{
return $this->method;
}
/**
* Get the request URI
*
* @return string The request URI
*/
public function getURI()
{
return $this->uri;
}
}
/**
* Class representing a SIP response message
*
* @author Chris Wright
* @version 1.0
*/
class SIPResponse extends SIPMessage
{
/**
* @var int The response code
*/
private $code;
/**
* @var string The response message
*/
private $message;
/**
* Constructor
*
* @param array $headers The request headers
* @param string $body The request body
* @param string $version The request protocol version
* @param int $code The response code
* @param string $message The response message
*/
public function __construct($headers, $body, $version, $code, $message)
{
parent::__construct($headers, $body, $version);
$this->code = $code;
$this->message = $message;
}
/**
* Get the response code
*
* @return int The response code
*/
public function getCode()
{
return $this->code;
}
/**
* Get the response message
*
* @return string The response message
*/
public function getMessage()
{
return $this->message;
}
}
/**
* Factory which makes SIP request objects
*
* @author Chris Wright
* @version 1.0
*/
class SIPRequestFactory extends RFC822MessageFactory
{
/**
* Create a new SIP request object
*
* The last 3 arguments of this method are only optional to prevent PHP from triggering
* an E_STRICT at compile time. IMO this particular error is itself an error on the part
* of the PHP designers, and I don't feel bad about about this workaround, even if it
* does mean the signature is technically wrong. It is the lesser of two evils.
*
* @param array $headers The request headers
* @param string $body The request body
* @param string $version The request protocol version
* @param string $method The request method
* @param string $uri The request URI
*/
public function create($headers, $body, $version = null, $method = null, $uri = null)
{
return new SIPRequest($headers, $body, $version, $method, $uri);
}
}
/**
* Factory which makes SIP response objects
*
* @author Chris Wright
* @version 1.0
*/
class SIPResponseFactory extends RFC822MessageFactory
{
/**
* Create a new SIP response object
*
* The last 3 arguments of this method are only optional to prevent PHP from triggering
* an E_STRICT at compile time. IMO this particular error is itself an error on the part
* of the PHP designers, and I don't feel bad about about this workaround, even if it
* does mean the signature is technically wrong. It is the lesser of two evils.
*
* @param array $headers The response headers
* @param string $body The response body
* @param string $version The response protocol version
* @param int $code The response code
* @param string $message The response message
*/
public function create($headers, $body, $version = null, $code = null, $message = null)
{
return new SIPResponse($headers, $body, $version, $code, $message);
}
}
/**
* Parser which creates SIP message objects from strings
*
* @author Chris Wright
* @version 1.0
*/
class SIPMessageParser extends RFC822MessageParser
{
/**
* @var SIPRequestFactory Factory which makes SIP request objects
*/
private $requestFactory;
/**
* @var SIPResponseFactory Factory which makes SIP response objects
*/
private $responseFactory;
/**
* Constructor
*
* @param SIPRequestFactory $requestFactory Factory which makes SIP request objects
* @param SIPResponseFactory $responseFactory Factory which makes SIP response objects
*/
public function __construct(SIPRequestFactory $requestFactory, SIPResponseFactory $responseFactory)
{
$this->requestFactory = $requestFactory;
$this->responseFactory = $responseFactory;
}
/**
* Remove the request line from the message and parse into tokens
*
* @param string $head The message head section
*
* @return array The parsed request line at index 0, the remainder of the message at index 1
*
* @throws \DomainException When the request line of the message is invalid
*/
private function removeAndParseRequestLine($head)
{
// Note: this method forgives a couple of minor standards violations, mostly for benefit
// of some older Polycom phones and for Voispeed, who seem to make stuff up as they go
// along. It also treats the whole line as case-insensitive even though methods are
// officially case-sensitive, because having two different casings of the same verb mean
// different things makes no sense semantically or implementationally.
// Side note, from RFC3261:
// > The SIP-Version string is case-insensitive, but implementations MUST send upper-case
// Wat. Go home Rosenberg, et. al., you're drunk.
$parts = preg_split('/\r?\n/', $head, 2);
$expr =
'@^
(?:
([^\r\n \t]+) [ \t]+ ([^\r\n \t]+) [ \t]+ SIP/(\d+\.\d+) # request
|
SIP/(\d+\.\d+) [ \t]+ (\d+) [ \t]+ ([^\r\n]+) # response
)
$@ix';
if (!preg_match($expr, $parts[0], $match)) {
throw new \DomainException('Request-Line of the message is invalid');
}
if (empty($match[4])) { // request
$requestLine = array(
'method' => strtoupper($match[1]),
'uri' => $match[2],
'version' => $match[3]
);
} else { // response
$requestLine = array(
'version' => $match[4],
'code' => (int) $match[5],
'message' => $match[6]
);
}
return array(
$requestLine,
isset($parts[1]) ? $parts[1] : ''
);
}
/**
* Create the appropriate message object from a string
*
* @param string $message The message string
*
* @return SIPRequest|SIPResponse The parsed message object
*
* @throws \DomainException When the message string is not valid SIP message
*/
public function parseMessage($message)
{
list($head, $body) = $this->splitHeadFromBody($message);
list($requestLine, $head) = $this->removeAndParseRequestLine($head);
$headers = $this->parseHeaders($head);
if (isset($requestLine['uri'])) {
return $this->requestFactory->create(
$headers,
$body,
$requestLine['version'],
$requestLine['method'],
$requestLine['uri']
);
} else {
return $this->responseFactory->create(
$headers,
$body,
$requestLine['version'],
$requestLine['code'],
$requestLine['message']
);
}
}
}
1 つのヘッダー値を抽出するためだけに大量のコードのように思えませんか? そうですね。しかし、それだけではありません。メッセージ全体をデータ構造に解析して、任意の数の情報に簡単にアクセスできるようにし、標準が投げかける可能性のあるものを (多かれ少なかれ) 許可します。
それでは、実際にどのように使用するかを見てみましょう。
// First we create a parser object
$messageParser = new SIPMessageParser(
new SIPRequestFactory,
new SIPResponseFactory
);
// Parse the message into an object
try {
$message = $messageParser->parseMessage($msg);
} catch (Exception $e) {
// The message parsing failed, handle the error here
}
// Get the value of the Via: header
$via = $message->getHeader('Via');
// SIP is irritatingly non-specific about the format of branch IDs. This
// expression matches either a quoted string or an unquoted token, which is
// about all that you can say for sure about arbitrary implementations.
$expr = '/branch=(?:"((?:[^"\\\\]|\\\\.)*)"|(.+?)(?:\s|;|$))/i';
// NB: this assumes the message has a single Via: header and a single branch ID.
// In reality this is rarely the case for messages that are received, although
// it is usually the case for messages before they are sent.
if (!preg_match($expr, $via[0], $matches)) {
// The Via: header does not contain a branch ID, handle this error
}
$branchId = !empty($matches[2]) ? $matches[2] : $matches[1];
var_dump($branchId);
動いているのを見る
この答えは、目前の問題に対してほぼ間違いなく非常にやり過ぎです。しかし、私はそれがこの問題に取り組む正しい方法だと考えています。