diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 96926ab..0010a2c 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -2231,6 +2231,9 @@ function _drupal_bootstrap_configuration() {
 
   // Activate the autoloader.
   $loader->register();
+
+  // Include Services.
+  require_once(DRUPAL_ROOT . '/includes/services.inc');
 }
 
 /**
diff --git a/includes/services.inc b/includes/services.inc
new file mode 100644
index 0000000..c4e5742
--- /dev/null
+++ b/includes/services.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Wrapper around HTTP request. Uses Symfony HTTPFoundation component
+ * to collect the data.
+ */
+class DrupalRequestWrapper implements ArrayAccess {
+
+  private $container = array();
+
+  public function __construct() {
+    $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
+    $this->container = array(
+      'method' => $request->getMethod(),
+      'url' => $request->getUri(),
+      'accept_types' => $request->getAcceptableContentTypes(),
+      'domain' => $request->getHost(),
+      'request_args' => $request->request->all(),
+      'query_args' => $request->query->all(),
+      'languages' => $request->getLanguages(),
+      'files' => $request->files->all(),
+      'cookies' => $request->cookies->all(),
+      'headers' => $request->headers->all(),
+      'server' => $request->server->all(),
+      'request_body' => $request->getContent(),
+      'request' => $request,
+    );
+  }
+
+  public function offsetSet($offset, $value) {
+    if (is_null($offset)) {
+      $this->container[] = $value;
+    }
+    else {
+      $this->container[$offset] = $value;
+    }
+  }
+
+  public function offsetExists($offset) {
+    return isset($this->container[$offset]);
+  }
+
+  public function offsetUnset($offset) {
+    unset($this->container[$offset]);
+  }
+
+  public function offsetGet($offset) {
+    return isset($this->container[$offset]) ? $this->container[$offset] : null;
+  }
+}
\ No newline at end of file
diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info
index 171eea1..82d1edc 100644
--- a/modules/simpletest/simpletest.info
+++ b/modules/simpletest/simpletest.info
@@ -32,6 +32,7 @@ files[] = tests/path.test
 files[] = tests/registry.test
 files[] = tests/schema.test
 files[] = tests/session.test
+files[] = tests/services.test
 files[] = tests/symfony.test
 files[] = tests/tablesort.test
 files[] = tests/theme.test
diff --git a/modules/simpletest/tests/services.test b/modules/simpletest/tests/services.test
new file mode 100644
index 0000000..ae13391
--- /dev/null
+++ b/modules/simpletest/tests/services.test
@@ -0,0 +1,302 @@
+<?php
+
+/**
+ * Basic test case for testing Services functionality.
+ */
+class ServicesTestCase extends DrupalWebTestCase {
+
+  protected function servicesGet($url, $data = NULL, $headers = array()) {
+    $options = array('query' => $data);
+    $url = url($this->getAbsoluteUrl($url), $options);
+
+    $content = $this->curlExec(array(
+      CURLOPT_HTTPGET => TRUE,
+      CURLOPT_URL => $url,
+      CURLOPT_NOBODY => FALSE,
+      CURLOPT_RETURNTRANSFER => TRUE,
+      CURLOPT_HEADER => TRUE,
+      CURLOPT_HTTPHEADER => $this->prepareHeaders($headers),
+    ));
+
+    // Parse response.
+    list($info, $header, $status, $code, $body) = $this->parseHeader($content);
+
+    $this->verbose('GET request to: ' . $url .
+                   '<hr />Arguments: ' . highlight_string('<?php ' . var_export($data, TRUE), TRUE) .
+                   '<hr />Response: ' . highlight_string('<?php ' . var_export($body, TRUE), TRUE) .
+                   '<hr />Raw response: ' . $content);
+    return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body);
+  }
+
+  protected function servicesPost($url, $data = array(), $headers = array()) {
+    $url = $this->getAbsoluteUrl($url);
+
+    // Otherwise Services will reject arguments.
+//    $headers += array('Content-type' => 'application/x-www-form-urlencoded');
+
+    // Prepare arguments.
+    $post = drupal_http_build_query($data, '', '&');
+
+    $content = $this->curlExec(array(
+      CURLOPT_URL => $url,
+      CURLOPT_POST => TRUE,
+      CURLOPT_POSTFIELDS => $post,
+      CURLOPT_HTTPHEADER => $this->prepareHeaders($headers),
+      CURLOPT_HEADER => TRUE,
+      CURLOPT_RETURNTRANSFER => TRUE
+    ));
+
+    // Parse response.
+    list($info, $header, $status, $code, $body) = $this->parseHeader($content);
+
+    $this->verbose('POST request to: ' . $url .
+                   '<hr />Arguments: ' . highlight_string('<?php ' . var_export($data, TRUE), TRUE) .
+                   '<hr />Response: ' . highlight_string('<?php ' . var_export($body, TRUE), TRUE) .
+                   '<hr />Curl info: ' . highlight_string('<?php ' . var_export($info, TRUE), TRUE) .
+                   '<hr />Raw response: ' . $content);
+    return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body);
+  }
+
+  protected function servicesPut($url, $data = NULL, $headers = array()) {
+    $url = $this->getAbsoluteUrl($url);
+
+    $put = serialize($data);
+
+    // Set up headers so arguments will be unserialized.
+    $headers += array('Content-type' => 'application/vnd.php.serialized; charset=iso-8859-1');
+
+    // Emulate file.
+    $putData = fopen('php://temp', 'rw+');
+    fwrite($putData, $put);
+    fseek($putData, 0);
+
+    $content = $this->curlExec(array(
+      CURLOPT_URL => $url,
+      CURLOPT_RETURNTRANSFER => TRUE,
+      CURLOPT_PUT => TRUE,
+      CURLOPT_HEADER => TRUE,
+      CURLOPT_HTTPHEADER => $this->prepareHeaders($headers),
+      CURLOPT_INFILE => $putData,
+      CURLOPT_INFILESIZE => drupal_strlen($put)
+    ));
+    fclose($putData);
+
+    // Parse response.
+    list($info, $header, $status, $code, $body) = $this->parseHeader($content);
+
+    $this->verbose('PUT request to: ' . $url .
+                   '<hr />Arguments: ' . highlight_string('<?php ' . var_export($data, TRUE), TRUE) .
+                   '<hr />Response: ' . highlight_string('<?php ' . var_export($body, TRUE), TRUE) .
+                   '<hr />Curl info: ' . highlight_string('<?php ' . var_export($info, TRUE), TRUE) .
+                   '<hr />Raw response: ' . $content);
+    return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body);
+  }
+
+  protected function servicesDelete($url, $data = NULL, $headers = array()) {
+    $options = array('query' => $data);
+    $url = url($this->getAbsoluteUrl($url), $options);
+
+    $content = $this->curlExec(array(
+      CURLOPT_URL => $url,
+      CURLOPT_CUSTOMREQUEST => "DELETE",
+      CURLOPT_HTTPHEADER => $this->prepareHeaders($headers),
+      CURLOPT_RETURNTRANSFER => TRUE
+    ));
+
+    // Parse response.
+    list($info, $header, $status, $code, $body) = $this->parseHeader($content);
+
+    $this->verbose('DELETE request to: ' . $url .
+                   '<hr />Arguments: ' . highlight_string('<?php ' . var_export($data, TRUE), TRUE) .
+                   '<hr />Response: ' . highlight_string('<?php ' . var_export($body, TRUE), TRUE) .
+                   '<hr />Curl info: ' . highlight_string('<?php ' . var_export($info, TRUE), TRUE) .
+                   '<hr />Raw response: ' . $content);
+    return array('header' => $header, 'status' => $status, 'code' => $code, 'body' => $body);
+  }
+
+  /*
+  ------------------------------------
+  HELPER METHODS
+  ------------------------------------
+  */
+
+  /**
+   * Parse header.
+   *
+   * @param type $content
+   * @return type
+   */
+  function parseHeader($content) {
+    $info = curl_getinfo($this->curlHandle);
+    $header = drupal_substr($content, 0, $info['header_size']);
+    $header = str_replace("HTTP/1.1 100 Continue\r\n\r\n", '', $header);
+    $status = strtok($header, "\r\n");
+    $code = $info['http_code'];
+    $body = unserialize(drupal_substr($content, $info['header_size'], drupal_strlen($content) - $info['header_size']));
+    return array($info, $header, $status, $code, $body);
+  }
+
+  /**
+   * Prepare headers array for curl argument.
+   *
+   * @param array $headers_array
+   * @return string
+   */
+  function prepareHeaders($headers_array) {
+    $headers = array();
+    foreach ($headers_array as $key => $value) {
+      $headers[] = $key . ': ' . $value;
+    }
+    return $headers;
+  }
+
+  /**
+   * Performs a cURL exec with the specified options after calling curlConnect().
+   *
+   * @param $curl_options
+   *   Custom cURL options.
+   * @return
+   *   Content returned from the exec.
+   */
+  protected function curlExec($curl_options, $redirect = FALSE) {
+    $this->curlInitialize();
+
+    // cURL incorrectly handles URLs with a fragment by including the
+    // fragment in the request to the server, causing some web servers
+    // to reject the request citing "400 - Bad Request". To prevent
+    // this, we strip the fragment from the request.
+    // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
+    if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) {
+      $original_url = $curl_options[CURLOPT_URL];
+      $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
+    }
+
+    $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
+
+    if (!empty($curl_options[CURLOPT_POST])) {
+      // This is a fix for the Curl library to prevent Expect: 100-continue
+      // headers in POST requests, that may cause unexpected HTTP response
+      // codes from some webservers (like lighttpd that returns a 417 error
+      // code). It is done by setting an empty "Expect" header field that is
+      // not overwritten by Curl.
+      $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
+    }
+    curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
+
+    if (!$redirect) {
+      // Reset headers, the session ID and the redirect counter.
+      $this->session_id = NULL;
+      $this->headers = array();
+      $this->redirect_count = 0;
+    }
+
+    $content = curl_exec($this->curlHandle);
+    $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
+
+    // cURL incorrectly handles URLs with fragments, so instead of
+    // letting cURL handle redirects we take of them ourselves to
+    // to prevent fragments being sent to the web server as part
+    // of the request.
+    // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
+    if (in_array($status, array(300, 301, 302, 303, 305, 307)) && $this->redirect_count < variable_get('simpletest_maximum_redirects', 5)) {
+      if ($this->drupalGetHeader('location')) {
+        $this->redirect_count++;
+        $curl_options = array();
+        $curl_options[CURLOPT_URL] = $this->drupalGetHeader('location');
+        $curl_options[CURLOPT_HTTPGET] = TRUE;
+        return $this->curlExec($curl_options, TRUE);
+      }
+    }
+
+    $this->drupalSetContent($content, isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
+
+    // Analyze the method for log message.
+    $method = '';
+    if (!empty($curl_options[CURLOPT_NOBODY])) {
+      $method = 'HEAD';
+    }
+
+    if (empty($method) && !empty($curl_options[CURLOPT_PUT])) {
+      $method = 'PUT';
+    }
+
+    if (empty($method) && !empty($curl_options[CURLOPT_CUSTOMREQUEST])) {
+      $method = $curl_options[CURLOPT_CUSTOMREQUEST];
+    }
+
+    if (empty($method)) {
+      $method = empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST';
+    }
+    $message_vars = array(
+      '!method' => $method,
+      '@url' => isset($original_url) ? $original_url : $url,
+      '@status' => $status,
+      '!length' => format_size(drupal_strlen($this->drupalGetContent()))
+    );
+    $message = t('!method @url returned @status (!length).', $message_vars);
+    $this->assertTrue($this->drupalGetContent() !== FALSE, $message, t('Browser'));
+    return $this->drupalGetContent();
+  }
+}
+
+class ServicesExampleTest extends ServicesTestCase {
+
+  /**
+   * Implements getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Example Services test'),
+      'description' => t('Test basic functionality.'),
+      'group' => t('Services'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('services_test');
+  }
+
+  function testExampleTest() {
+    // Do GET call.
+    $args = array('foo' => 'bar');
+    $response = $this->servicesGet('services-test', $args);
+    $body = $response['body'];
+    $this->assertEqual($body['method'], 'GET', t('Request call type %method recognized.', array('%method' => 'GET')));
+    $this->assertTrue(isset($body['query_args']['foo']) && $body['query_args']['foo'] == 'bar', t('Query argument received properly.'));
+
+    // Do POST call.
+    $args = array('foo' => 'bar');
+    $response = $this->servicesPost('services-test', $args);
+    $body = $response['body'];
+    $this->assertEqual($body['method'], 'POST', t('Request call type %method recognized.', array('%method' => 'POST')));
+    $this->assertTrue(isset($body['request_args']['foo']) && $body['request_args']['foo'] == 'bar', t('Request argument received properly.'));
+
+    // Do PUT call.
+    $args = array('foo' => 'bar');
+    $response = $this->servicesPut('services-test', $args);
+    $body = $response['body'];
+    $this->assertEqual($body['method'], 'PUT', t('Request call type %method recognized.', array('%method' => 'PUT')));
+    $this->assertTrue(isset($body['query_args']['foo']) && $body['query_args']['foo'] == 'bar', t('Query argument received properly.'));
+
+    // Do DELETE call.
+    $args = array('foo' => 'bar');
+    $response = $this->servicesDelete('services-test', $args);
+    $body = $response['body'];
+    $this->assertEqual($body['method'], 'DELETE', t('Request call type %method recognized.', array('%method' => 'DELETE')));
+    $this->assertTrue(isset($body['query_args']['foo']) && $body['query_args']['foo'] == 'bar', t('Query argument received properly.'));
+
+    // Emulate POST call from json application.
+    // Content types:
+    // @see http://en.wikipedia.org/wiki/Internet_media_type
+//    $headers = array(
+//      'Content-type' => 'application/json; charset=UTF-8'
+//    );
+//    $args = array('foo' => 'bar');
+//    $response = $this->curlExec(NULL, array(
+//      CURLOPT_HTTPHEADER => $headers,
+//      CURLOPT_POSTFIELDS => json_encode($args),
+//    ));
+//
+//    debug($response);
+  }
+}
diff --git a/modules/simpletest/tests/services_test.info b/modules/simpletest/tests/services_test.info
new file mode 100644
index 0000000..0411bf8
--- /dev/null
+++ b/modules/simpletest/tests/services_test.info
@@ -0,0 +1,6 @@
+name = "Services test module"
+description = "Tests for the services".
+package = Testing
+version = VERSION
+core = 8.x
+;hidden = TRUE
\ No newline at end of file
diff --git a/modules/simpletest/tests/services_test.module b/modules/simpletest/tests/services_test.module
new file mode 100644
index 0000000..7ef268a
--- /dev/null
+++ b/modules/simpletest/tests/services_test.module
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Implements hook_menu().
+ *
+ * Provides test menu callback.
+ */
+function services_test_menu() {
+  $items['services-test'] = array(
+    'title' => 'Services test',
+    'page callback' => '_services_test_callback',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Print serialized http wrapper.
+ */
+function _services_test_callback() {
+  $http_wrapper = new DrupalRequestWrapper();
+
+  print_r(serialize($http_wrapper));
+
+  // Add log record.
+  watchdog('services-test', var_export($http_wrapper, TRUE));
+
+  // Do not let this output
+  module_invoke_all('exit');
+  exit;
+}
