1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-09-21 01:18:50 +02:00
phorge-arcanist/src/hgdaemon/ArcanistHgServerChannel.php
epriestley f9d55d809f Rough cut of hgdaemon
Summary:
This need a bunch of work, but is a sort of minimum viable product for the hgdaemon.

The two ProtocolChannels implement the client and server halves of the protocol.

The Server implements the server logic, the Client implements the client logic.

Test Plan: Launched a server and client on the same repo, got hg output in ~2-3ms instead of 100ms+.

Reviewers: csilvers, btrahan, vrana

Reviewed By: csilvers

CC: aran

Differential Revision: https://secure.phabricator.com/D2665
2012-06-07 18:23:57 -07:00

193 lines
5.4 KiB
PHP

<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Channel to a Mercurial "cmdserver" server. Messages sent to the server
* look like this:
*
* runcommand\n
* 8 # Length, as a 4-byte unsigned long.
* log\0
* -l\0
* 5
*
* In PHP, the format of these messages is an array of arguments:
*
* array(
* 'runcommand',
* 'log',
* '-l',
* '5',
* );
*
* The server replies with messages that look like this:
*
* o
* 1234 # Length, as a 4-byte unsigned long.
* <data: 1234 bytes>
*
* The first character in a message from the server is the "channel". Mercurial
* channels have nothing to do with Phutil channels; they are more similar to
* stdout/stderr. Mercurial has four primary channels:
*
* 'o'utput, like stdout
* 'e'rror, like stderr
* 'r'esult, like return codes
* 'd'ebug, like an external log file
*
* In PHP, the format of these messages is a pair, with the channel and then
* the data:
*
* array('o', '<data...>');
*
* In general, we send "runcommand" requests, and the server responds with
* a series of messages on the "output" channel and then a single response
* on the "result" channel to indicate that output is complete.
*
* @task protocol Protocol Implementation
*/
final class ArcanistHgServerChannel extends PhutilProtocolChannel {
const MODE_CHANNEL = 'channel';
const MODE_LENGTH = 'length';
const MODE_BLOCK = 'block';
private $mode = self::MODE_CHANNEL;
private $byteLengthOfNextChunk = 1;
private $buf = '';
/* -( Protocol Implementation )-------------------------------------------- */
/**
* Encode a message for transmission to the server. The message should be
* formatted as an array, like this:
*
* array(
* 'runcommand',
* 'log',
* '-l',
* '5',
* );
*
*
* We will return the cmdserver version of this:
*
* runcommand\n
* 8 # Length, as a 4-byte unsigned long.
* log\0
* -l\0
* 5
*
* @param list<string> List of command arguments.
* @return string Encoded string for transmission to the server.
*
* @task protocol
*/
protected function encodeMessage($argv) {
if (!is_array($argv)) {
throw new Exception("Message to Mercurial server should be an array.");
}
$command = head($argv);
$args = array_slice($argv, 1);
$args = implode("\0", $args);
$len = strlen($args);
$len = pack('N', $len);
return "{$command}\n{$len}{$args}";
}
/**
* Decode a message received from the server. The message looks like this:
*
* o
* 1234 # Length, as a 4-byte unsigned long.
* <data: 1234 bytes>
*
* ...where 'o' is the "channel" the message is being sent over.
*
* We decode into a pair in PHP, which looks like this:
*
* array('o', '<data...>');
*
* @param string Bytes from the server.
* @return list<pair<string,string>> Zero or more complete messages.
*
* @task protocol
*/
protected function decodeStream($data) {
$this->buf .= $data;
// We always know how long the next chunk is, so this parser is fairly
// easy to implement.
$messages = array();
while ($this->byteLengthOfNextChunk <= strlen($this->buf)) {
$chunk = substr($this->buf, 0, $this->byteLengthOfNextChunk);
$this->buf = substr($this->buf, $this->byteLengthOfNextChunk);
switch ($this->mode) {
case self::MODE_CHANNEL:
// We've received the channel name, one of 'o', 'e', 'r' or 'd' for
// 'output', 'error', 'result' or 'debug' respectively. This is a
// single byte long. Next, we'll expect a length.
$this->channel = $chunk;
$this->byteLengthOfNextChunk = 4;
$this->mode = self::MODE_LENGTH;
break;
case self::MODE_LENGTH:
// We've received the length of the data, as a 4-byte big-endian
// unsigned integer. Next, we'll expect the data itself.
$this->byteLengthOfNextChunk = head(unpack('N', $chunk));
$this->mode = self::MODE_BLOCK;
break;
case self::MODE_BLOCK:
// We've received the data itself, which is a block of bytes of the
// given length. We produce a message from the channel and the data
// and return it. Next, we expect another channel name.
$message = array($this->channel, $chunk);
$this->byteLengthOfNextChunk = 1;
$this->mode = self::MODE_CHANNEL;
$this->channel = null;
$messages[] = $message;
break;
}
}
// Return zero or more messages, which might look something like this:
//
// array(
// array('o', '<...>'),
// array('o', '<...>'),
// array('r', '<...>'),
// );
return $messages;
}
}