请正确解析您的 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']
);
}
}
}
似乎有很多代码只是为了提取一个标头值,不是吗?嗯,是的。但这不仅仅是它的作用。它将整个消息解析为一个数据结构,该结构提供对任意数量信息的轻松访问,允许(或多或少)标准可能向您抛出的任何内容。
那么,让我们看看你将如何实际使用它:
// 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);
看到它工作
对于手头的问题,这个答案几乎可以肯定是大材小用。但是,我认为这是解决这个问题的正确方法。