blob: 18774960eebd5c0f8544aef0d70a68141046c550 [file] [log] [blame]
<?php
/**
* Copyright 2007 Google 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.
*/
/**
* The PushTask class, which is part of the Task Queue API.
*
*/
# Overview of TODOs(petermck) for building out the full Task Queue API:
# - Support additional options for PushTasks, including retry options and maybe
# raw payloads.
# - Support various queue level functionality such as FetchQueueStats.
# - Add PullTask class. At that point, perhaps refactor to use a Task
# baseclass to share code with PushTask.
# - Add a PullQueue class, including pull specific queue methods such as
# leaseTasks, DeleteTasks etc.
# - Consider adding a Queue base class with common functionality between Push
# and Pull queues.
namespace google\appengine\api\taskqueue;
require_once 'google/appengine/api/taskqueue/PushQueue.php';
require_once 'google/appengine/api/taskqueue/taskqueue_service_pb.php';
use google\appengine\TaskQueueAddRequest\RequestMethod;
/**
* A PushTask encapsulates a unit of work that an application places onto a
* Push Queue for asnychronous execution. The queue executes that work by
* sending the task back to the application in the form of an HTTP request to
* one of the application's handlers.
* This class is immutable.
*/
final class PushTask {
/**
* A task may be scheduled up to 30 days into the future.
*/
const MAX_DELAY_SECONDS = 2592000;
const MAX_NAME_LENGTH = 500;
const MAX_TASK_SIZE_BYTES = 102400;
const MAX_URL_LENGTH = 2083;
const NAME_PATTERN = '/^[a-zA-Z0-9_-]+$/';
private static $methods = [
'POST' => RequestMethod::POST,
'GET' => RequestMethod::GET,
'HEAD' => RequestMethod::HEAD,
'PUT' => RequestMethod::PUT,
'DELETE' => RequestMethod::DELETE
];
private static $default_options = [
'delay_seconds' => 0.0,
'method' => 'POST',
'name' => '',
'header' => '',
];
private $url;
private $query_data;
private $options;
private $headers = [];
/**
* Construct a PushTask.
*
* @param string $url_path The path of the URL handler for this task relative
* to your application's root directory.
* @param array $query_data The data carried by task, typically in the form of
* a set of key value pairs. This data will be encoded using
* http_build_query() and will be either:
* <ul>
* <li>Added to the payload of the http request if the task's method is POST
* or PUT.</li>
* <li>Added to the URL if the task's method is GET, HEAD, or DELETE.</li>
* </ul>
* @param array $options Additional options for the task. Valid options are:
* <ul>
* <li>'method': string One of 'POST', 'GET', 'HEAD', 'PUT', 'DELETE'.
* Default value: 'POST'.</li>
* <li>'name': string Name of the task. Defaults to '' meaning the service
* will generate a unique task name.</li>
* <li>'delay_seconds': float The minimum time to wait before executing the
* task. Default: zero.</li>
* <li>'header': string Additional headers to be sent when the task
* executes.</li>
* </ul>
*/
public function __construct($url_path, $query_data = [], $options = []) {
if (!is_string($url_path)) {
throw new \InvalidArgumentException('url_path must be a string. ' .
'Actual type: ' . gettype($url_path));
}
if (empty($url_path) || $url_path[0] !== '/') {
throw new \InvalidArgumentException(
'url_path must begin with \'/\'.');
}
if (strpos($url_path, '?') !== false) {
throw new \InvalidArgumentException(
'query strings not allowed in url_path.');
}
if (!is_array($query_data)) {
throw new \InvalidArgumentException('query_data must be an array. ' .
'Actual type: ' . gettype($query_data));
}
if (!is_array($options)) {
throw new \InvalidArgumentException('options must be an array. ' .
'Actual type: ' . gettype($options));
}
$extra_options = array_diff(array_keys($options),
array_keys(self::$default_options));
if (!empty($extra_options)) {
throw new \InvalidArgumentException('Invalid options supplied: ' .
implode(',', $extra_options));
}
$this->options = array_merge(self::$default_options, $options);
if (!array_key_exists($this->options['method'], self::$methods)) {
throw new \InvalidArgumentException('Invalid method: ' .
$this->options['method']);
}
$name = $this->options['name'];
if (!is_string($name)) {
throw new \InvalidArgumentException('name must be a string. ' .
'Actual type: ' . gettype($name));
}
if (!empty($name)) {
if (strlen($name) > self::MAX_NAME_LENGTH) {
$display_len = 1000;
throw new \InvalidArgumentException('name exceeds maximum length of ' .
self::MAX_NAME_LENGTH . ". First $display_len characters of name: "
. substr($name, 0, $display_len));
}
if (!preg_match(self::NAME_PATTERN, $name)) {
throw new \InvalidArgumentException('name must match pattern: ' .
self::NAME_PATTERN . '. name: ' . $name);
}
}
$delay = $this->options['delay_seconds'];
if (!(is_double($delay) || is_long($delay))) {
throw new \InvalidArgumentException(
'delay_seconds must be a numeric type.');
}
if ($delay < 0 || $delay > self::MAX_DELAY_SECONDS) {
throw new \InvalidArgumentException(
'delay_seconds must be between 0 and ' . self::MAX_DELAY_SECONDS .
' (30 days). delay_seconds: ' . $delay);
}
$this->query_data = $query_data;
$this->url = $url_path;
if ($query_data) {
if (in_array($this->options['method'], ['GET', 'HEAD', 'DELETE'])) {
$this->url = $url_path . '?' . http_build_query($query_data);
} else { // PUT or POST
$this->headers[] = 'content-type: application/x-www-form-urlencoded';
}
}
if (strlen($this->url) > self::MAX_URL_LENGTH) {
throw new \InvalidArgumentException(
'URL length greater than maximum of ' .
self::MAX_URL_LENGTH . '. URL: ' . $this->url);
}
// Handle user specified headers.
$header = $this->options['header'];
if (!is_string($header)) {
throw new \InvalidArgumentException('header must be a string. ' .
'Actual type: ' . gettype($header));
}
$has_content_type = !empty($this->headers);
$header_array = explode("\r\n", $header);
foreach ($header_array as $h) {
$h = trim($h);
if (empty($h)) {
continue;
}
if (strpos($h, ':') === false) {
throw new \InvalidArgumentException(
'Each header must contain a colon. Header: ' . $h);
}
if ($has_content_type &&
strncasecmp('content-type', $h, strlen('content-type')) == 0) {
throw new \InvalidArgumentException('Content-type header may not ' .
'be specified as it is set by the task.');
}
$this->headers[] = $h;
}
}
/**
* Return the task's URL. This will be the task's URL path, plus any query
* parameters if the task's method is GET, HEAD, or DELETE.
*
* @return string The task's URL path.
*/
public function getUrl() {
return $this->url;
}
/**
* Return the task's query data.
*
* @return array The task's query data.
*/
public function getQueryData() {
return $this->query_data;
}
/**
* Return the task's name if it was explicitly named.
*
* @return string The task's name if it was explicity named, or empty string
* if it will be given a uniquely generated name in the queue.
*/
public function getName() {
return $this->options['name'];
}
/**
* Return the task's execution delay, in seconds.
*
* @return float The task's execution delay in seconds.
*/
public function getDelaySeconds() {
return $this->options['delay_seconds'];
}
/**
* Return the task's HTTP method.
*
* @return string The task's HTTP method, i.e. one of 'DELETE', 'GET', 'HEAD',
* 'POST', 'PUT'.
*/
public function getMethod() {
return $this->options['method'];
}
/**
* Return the task's headers.
*
* @return string[] The headers that will be sent when the task is
* executed. This list is not exhaustive as the backend may add more
* headers at execution time.
* The array is numerically indexed and of the same format as that returned
* by the standard headers_list() function.
*/
public function getHeaders() {
return $this->headers;
}
/**
* Adds the task to a queue.
*
* @param string $queue The name of the queue to add to. Defaults to
* 'default'.
*
* @return string The name of the task.
*
* @throws TaskAlreadyExistsException if a task of the same name already
* exists in the queue.
* @throws TaskQueueException if there was a problem using the service.
*/
public function add($queue_name = 'default') {
$queue = new PushQueue($queue_name);
return $queue->addTasks([$this])[0];
}
}