内容


在 Bluemix 上使用 PHP 和 MySQL 构建和部署 REST API

Comments

简介

如果您正在构建与在线产品或服务交互的应用程序,那么很可能您正在使用 REST API 提取数据或推入数据。在过去几年里,REST API 越来越受欢迎,因为它们易于理解,可以快速编码,并且可以通过内置 HTTP 支持在所有编程语言中使用。

如果您正在开发自己的云服务,那么 REST API 是一种促进数据共享和重用的出色方式。如果允许外部开发人员通过 REST 访问您的数据,他们就可以轻松地构建酷炫的新应用程序,并以一些有趣的方式利用您的产品或数据。考虑一下围绕 Facebook、Twitter 或 Instagram 开发的应用程序生态系统,好处显而易见。

“如果允许外部开发人员通过 REST 访问您的数据,他们就可以轻松地构建酷炫的新应用程序,并以一些有趣的方式利用您的产品或数据。”

本文提供了一个速成课程,介绍了如何使用 PHP 微型框架 Bullet 创建 REST API。除了解释如何实现 4 种基本的 REST 方法之外,本文还介绍了如何为常见的功能(比如 API 身份验证和多种数据格式支持)添加支持,然后将 API 部署到 Bluemix。来吧,让我们开始吧!

探索 API获取代码

了解 REST API 基础知识

首先,我们需要花费几分钟时间了解一下 REST,即 Representational State Transfer(具象状态传输)。REST 是一种基于 “资源” 和 “操作” 的 API 开发风格。资源是引用想要执行操作的对象或实体的 URL(例如,/users 或 /photos),而操作是 4 个 HTTP 动词之一:

  • GET(检索)
  • POST(创建)
  • PUT (更新)
  • DELETE(删除)

下面的示例明确说明了这一点。假设您有一个专为跟踪软件缺陷设计的应用程序,而且您想轻松地重用和操作应用程序数据库中的数据。您已经显示了一个 URL 端点(将其称为 /bugs),并允许外部开发人员使用不同的 HTTP 方法和内容访问此端点(例如,使用 GET /bugs 列出所有缺陷,或者是使用 DELETE /bugs/78 删除编号为 78 的缺陷)。

基于 HTTP 方法和内容,可以推导出正在请求的操作,并对数据采取相应的操作。下面是一些示例:

  • GET /documents:检索一个文档列表。
  • GET /bugs/123:检索编号为 123 的缺陷。
  • POST /photos:使用 POST 请求正文创建一个新照片。
  • PUT /photos/123:使用 PUT 请求正文更新编号为 123 的照片。
  • DELETE /orders/123:删除编号为 123 的订单。

您需要的内容

本文中开发的示例 REST API 假设一个产品数据库,并侧重于使用常规 REST 协定支持您检索、添加、删除和更新这些产品。为简单起见,Product 资源仅有三个属性:惟一的标识符、名称和价格。我还进一步假设产品数据保存在一个 MySQL 数据库中,并且 API 请求和响应是使用 JSON 编码的(尽管在后面的部分中,我还介绍了使用 XML 作为一种替代方法)。

下面是您需要的内容:

步骤 1:设置应用程序数据库

使用下列 MySQL 表定义和示例数据设置应用程序数据库。

  • 如果您只在本地进行开发和部署,那么可以使用这种方法针对要连接的 API 初始化 MySQL 数据库表。
  • 如果在 Bluemix 上进行部署,那么暂时可以跳过这一步;完成了步骤 7 在 Bluemix 上初始化和绑定 MySQL 服务实例后,再返回到这一步。
CREATE TABLE IF NOT EXISTS `products` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `price` decimal(5,2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `products` (`id`, `name`, `price`) VALUES
(1, 'Garden spade', 15.99),
(2, 'Cotton hammock', 54.50),
(3, 'Single airbed', 35.49);

步骤 2:安装 Bullet 和 Eloquent

下一步是下载并设置 Bullet 微型框架。为什么要使用 Bullet 呢?因为它包含一个灵活的 URL 路由器,可以轻松地在自定义 URL 上创建和响应不同的 HTTP 方法,它还包含一个嵌套路由回调,可以简化常见重复任务(比如 API 身份验证)的执行。

为了简化数据库访问,我将使用 Eloquent,这是一个受欢迎的对象关系映射器(Object Relational Mapper,ORM),它使用 ActiveRecord 实现来简化数据库记录的使用。Eloquent 是 Laravel PHP 框架的一部分,但它也可以单独使用。

我将使用 Composer(PHP 依赖关系管理器)来下载和设置 Bullet 与 Eloquent。下面是 Composer 配置文件,可以将它保存到 $APP_ROOT/composer.json。在本文中,$APP_ROOT 指应用程序工作目录。

{
    "require": {
        "vlucas/bulletphp": "*",
        "illuminate/database": "*"
    }
}

现在,可以使用 Composer 的下列命令安装 Bullet 和 Eloquent:

shell> php composer.phar install

为了简化应用程序访问,还可以在开发环境中定义新的虚拟主机,并将其文档根目录指定为 $APP_ROOT。尽管此步骤是可选的,但建议使用它,因为它在 Bluemix 上创建了一个更接近目标部署环境的副本。

  • 要在 Apache 中为应用程序设置一个命名的虚拟主机,请打开 Apache 配置文件(httpd.conf 或 httpd-vhosts.conf)并添加下列行。
	NameVirtualHost 127.0.0.1
	<VirtualHost 127.0.0.1>
	    DocumentRoot "/usr/local/apache/htdocs/api"
	    ServerName api.localhost
	</VirtualHost>
  • 要在 nginx 中为应用程序设置一个命名的虚拟主机,请打开 nginx 配置文件 (nginx.conf) 并添加下列行。
	server {
	    server_name api.localhost;
	     root /usr/local/apache/htdocs/api;
	     try_files $uri /index.php;
	     
	     location ~ \.php$ {
	        try_files $uri =404;            
	        include fastcgi_params;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        # assumes you are using php-fcgi
        fastcgi_pass 127.0.0.1:90;
	    }        
	}

这些行定义一个新的虚拟主机,http://api.localhost/,它的文档根目录对应于 $APP_ROOT 目录(记住更新此目录以反映您自己的本地设置)。重启 Web 服务器以激活这些新的设置。请注意,您可能需要更新网络的本地 DNS 服务器,让它知道新的主机。

步骤 3:处理资源集合的 GET 请求

Bullet 每次解析一个 URL 段并使用回调响应特定的路径或参数模式。可以在每个 HTTP 方法上定义回调,这允许您对 GET 请求而不是 POST 请求执行不同的操作。

典型的 REST API 支持两种 GET 请求:第一种请求针对资源集合 (GET /products) ,而第二种请求针对具体资源 (GET /products/123)。首先编写代码来处理第一种场景:使用数据库的所有可用产品列表响应 GET /products 请求。使用下列代码更新 $APP_ROOT/index.php 文件。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

第一步是初始化 Composer 自动加载器,它负责根据需要加载所需的类。然后,创建一个新的 Bullet\App 对象来表示应用程序,并使用此应用程序对象的 path() 方法来设置端点 /v1/products 的嵌套 URL 路径。最里面的 path() 回调定义此端点支持的方法 — 在本例中,使用应用程序对象的 get() 方法来定义 GET 请求的处理程序。

路由处理程序使用 Eloquent 模型返回数据库的所有产品。由于此处理程序返回的是一个数组,因此 Bullet 自动假设响应格式应该是 JSON,使用 'Content-Type: application/json' 标头和 200 OK 成功代码来转换此数组,并将它发送回客户端。如果有任何错误,异常处理程序会捕捉错误,并使用 500 Internal Server Error 和一个包含错误消息的 JSON 正文将错误返回给客户端。

当收到传入请求时,通过执行 Bullet 应用程序,应用程序对象的 run() 方法(如上一个清单的最后一行所示)将所有内容聚集在一起。

此时,您可能会认为这一切都是有意义的,除了一个问题:此 Eloquent 模型来自何处?下一个清单会将会说明这一点,使用 GET 处理程序添加使用 Eloquent 所需的代码,如上所述。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// use Eloquent ORM
use Illuminate\Database\Capsule\Manager as Capsule;  
use Illuminate\Database\Schema\Blueprint as Schema;  
 
// create model for Eloquent ORM mapped to REST API resource
class Product extends Illuminate\Database\Eloquent\Model {
  public $timestamps = false;
}

// get MySQL service configuration from Bluemix
$services = getenv("VCAP_SERVICES");
$services_json = json_decode($services, true);
$mysql_config = $services_json["mysql-5.5"][0]["credentials"];
$db = $mysql_config["name"];
$host = $mysql_config["host"];
$port = $mysql_config["port"];
$username = $mysql_config["user"];
$password = $mysql_config["password"];

// initialize Eloquent ORM
$capsule = new Capsule;
 
$capsule->addConnection(array(
  'driver'    => 'mysql',
  'host'      => $host,
  'port'      => $port,
  'database'  => $db,
  'username'  => $username,
  'password'  => $password,
  'charset'   => 'utf8',
  'collation' => 'utf8_unicode_ci',
  'prefix'    => ''
));

$capsule->setAsGlobal();
$capsule->bootEloquent();


// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products
    // list all products
    $app->get(function() use ($app)  {
      $products = Product::all();
      return $products->toArray();
    });

  });
    
});

echo $app->run(new Bullet\Request());

Eloquent 随附提供了一个 Capsule 管理器实例,旨在简化 Laravel 外部的 Eloquent 使用。上一个清单演示了实际的 Capsule 实例,建立了一个到 MySQL 数据库的新连接,并使 Capsule 实例在全局可用。

由于本文的最终目的是将应用程序部署到 Bluemix,因此会从特殊的 Bluemix VCAP_SERVICES 环境变量提取 MySQL 连接的凭据;但是,如果您打算在本地进行部署,那么可以使用特定于本地数据库服务器的值取代这些值。

您还会注意到扩展 Illuminate\Database\Eloquent\Model 类的 Product 模型。此 Product 模型提供了许多预定义的方法,可以简化相应数据库记录的使用。您已经看到了其中的一个实际方法 —Product::all()—,它负责在内部生成对 "return all products in the database" 的 SELECT 查询。

下面是成功的 /v1/products GET 请求的结果。

/v1/products 的 GET 请求
/v1/products 的 GET 请求

步骤 4:处理新资源的 POST 请求

可以类似的方式处理 POST 请求并将它们转换为新产品记录。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // POST /v1/products
    // create new product
    $app->post(function($request) use ($app) {
      $product = new Product();
      $product->name = trim(htmlentities($request->name));
      $product->price = round(trim(htmlentities($request->price)), 2);
      if ($product->name && $product->price) {
        $product->save();
        return $app->response(201, $product->toArray());
      } else {
        return 400;
      }

    });

  });
    
});

echo $app->run(new Bullet\Request());

此清单为 /v1/products 的 POST 请求添加了一个新处理程序。当应用程序收到 POST 请求时,会创建一个空白的 Product 对象,根据请求对象设置 Product 'name' 和 'price',并调用模型的 save() 对象来为数据库添加新记录。

如果成功添加了记录,那么处理程序将会返回 201 Created 状态代码和新 Product 资源的 JSON 表示形式。如果没有成功,则会返回 400 Bad Request 或 500 Internal Server Error 错误代码和一条解释错误的 JSON 消息。在下面查看成功和失败的 POST 请求和响应示例。

/v1/products 的成功 POST 请求
/v1/products 的成功 POST 请求
/v1/products 的失败 POST 请求
/v1/products 的失败 POST 请求

值得注意的一个有趣现象是:在 POST 正文内容中,已创建的新 Product 资源被作为 JSON 对象传递给 API。但是,在 POST 处理程序中,您会看到没有使用 PHP 的 json_decode() 函数来解码 POST 有效负载。这是因为在 POST 或 PUT 请求中遇到 JSON 有效负载时 Bullet 会自动进行解码。然后,可以直接使用 JSON 对象属性作为请求对象的属性。

步骤 5:处理各个资源的 GET、PUT 和 DELETE 请求

在上一部分中,我介绍了如何处理资源集合的 GET 请求 (GET /v1/products)。但是,REST API 应该还支持使用惟一资源标识符直接访问资源(GET /v1/products/123DELETE /v1/products/123)。这使得可以使用 API 检索、更新或删除各个资源。

与之前看到的 path() 方法(针对静态 URL 路径)类似,Bullet 还包含一种 param() 方法,它旨在处理包含变量的 URL 路径。下列清单在具体资源的 GET 请求处理程序上下文中演示了这一方法。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
           
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });
           
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());

此清单以 /v1/products/[id] 的形式设置了 URL 的回调处理程序,其中 [id] 是一个变量路径段,表示惟一资源标识符。'int' 参数进一步指定了此变量是一个分配给 $id 变量的整数。

在此清单中,从 URL 路径捕获的资源标识符被传递给了 Product 模型,并且此模型的 find() 方法用于返回相应的数据库记录。然后,Bullet 将记录转换为 JSON 格式并将它返回给客户端。如果没有找到匹配的记录,处理程序会返回 404 Not Found 错误代码。如果存在错误,则异常处理程序会捕获错误并将它传递回 500 Internal Server Error 错误代码。

下面是成功的 GET 请求和响应的结果。

/v1/products/:id 的 GET 请求
/v1/products/:id 的 GET 请求

有了此结构,针对具体资源添加 DELETE 和 PUT 请求的支持就会非常简单。查看下列清单,此清单包含了此额外功能。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// initialize application
$app = new Bullet\App();
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
      
    $app->param('int', function($request, $id) use($app) {

      $product = Product::find($id);
      if(!$product) {
        return 404;
      }
                
      // GET /v1/products/:id
      // list single product by id
      $app->get(function($request) use($product, $app) {
        return $product->toArray();
      });

      // PUT /v1/products/:id
      // update product by id
      $app->put(function($request) use($product, $app) {
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(200, $product->toArray());
        } else {
          return 400;          
        }
      });
          
      // DELETE /v1/products/:id
      // delete product by id
      $app->delete(function($request) use($product) {
        $product->delete();
        return 204;
      });
      
    });        

  });
    
});

echo $app->run(new Bullet\Request());
  • PUT 请求意味着使用 PUT 请求正文的更新信息更新现有资源。此清单定义 PUT 处理程序,该程序读取 PUT 请求中的 JSON 文档,并使用 Eloquent 更新检索的 Product 对象的属性。然后,会使用此对象的 save() 方法将更新资源保存回数据库,并会将 200 Accepted 服务器响应代码返回给客户端,以此表示操作获得了成功并更新了资源。
  • DELETE 请求意味着要从数据存储区删除 DELETE URL 中引用的资源,而上面的 DELETE 处理程序正在进行这样的操作:它调用检索 Product 对象的 delete() 方法从 MySQL 数据库删除产品记录。在本例中,我们使用了 204 No Content 响应代码和一个空白的响应正文来表示成功。

下列图像显示了成功的 PUT 和 DELETE 请求。

/v1/products/:id 的 PUT 请求
/v1/products/:id 的 PUT 请求
/v1/products/:id 的 DELETE 请求
/v1/products/:id 的 DELETE 请求

在这里需要注意的一个有趣现象是:上面的三个嵌套路由处理程序都使用了父处理程序中创建的 $product 实例。在父处理程序中定义常见例程或对象,然后在所有子闭包或块中使用它们,这样做可以使得重复的代码变得更少,使维护和更新变得更简单。

步骤 6:支持多种格式

大多数情况下,使用 JSON 请求和响应正文就可以了。但是,如果您想要支持另一种格式(比如 XML)会怎么样?使用 Bullet,您可以为不同格式定义自定义响应器,这样您就可以轻松处理此需求。

为了说明这点,请考虑下一个清单,它为 /v1/products GET 端点的 XML 输出添加了支持:

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
    // GET /v1/products[.xml|.json]
    // list all products
    $app->get(function() use ($app)  {

      $products = Product::all();         
      
      // handle requests for XML content
      $app->format('xml', function($request) use($app, $products) {
        return $app->response(200, convert_array_to_xml($products->toArray()))
                      ->header('Content-Type', 'application/xml');
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app, $products) {
        return $products->toArray();
      });  
            
    });
        
  });    
  
});

echo $app->run(new Bullet\Request());

应用程序对象的 format() 方法用于针对 XML 和 JSON 输出定义单独的回调处理程序。当 Bullet 遇到 URL /v1/products.xml 的请求,或者具有 'Accept: application/xml' 标头的 URL /v1/products 请求时,它会自动调用 XML 回调处理程序。在后一种情况下,Bullet 使用了自动内容协商功能,以便了解请求格式并提供相应的输出。

从上一个清单中可以看出,此处理程序仅将从数据库检索到的一系列产品信息转换为 XML 文档,并将它返回给客户端。下列图像显示了输出。

/v1/products.xml 的 GET 请求
/v1/products.xml 的 GET 请求

要以 JSON 格式获得输出,客户端应该使用 'Accept: application/json' 标头请求 URL /v1/products.json 或 URL /v1/products,如下列输出所示。

/v1/products.json 的 GET 请求
/v1/products.json 的 GET 请求

可以类似的方式更新 POST 处理程序,因此它可以接受 XML 和 JSON 格式的输入。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize Eloquent ORM and model
// snipped

// function to convert array to XML
function convert_array_to_xml($data) {
  $xml = new SimpleXMLElement('<root/>');
  foreach ($data as $r) {
    $item = $xml->addChild('product');
    $item->addChild('id', $r['id']);
    $item->addChild('name', $r['name']);
    $item->addChild('price', $r['price']);
  }
  return $xml->asXml();
}

// initialize application
$app = new Bullet\App();

$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // POST /v1/products[.xml|.json]
    // create new product
    $app->post(function($request) use ($app) {

      // handle requests for XML content
      $app->format('xml', function($request) use($app) {
        $input = simplexml_load_string($request->raw());
        $product = new Product();
        $product->name = trim(htmlentities((string)$input->name));
        $product->price = round(trim(htmlentities((string)$input->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, convert_array_to_xml(array($product->toArray())))
                    ->header('Content-Type', 'application/xml');          
        } else {
          return 400;
        }
      });
        
      // handle requests for JSON content
      $app->format('json', function($request) use($app) {          
        $product = new Product();
        $product->name = trim(htmlentities($request->name));
        $product->price = round(trim(htmlentities($request->price)), 2);
        if ($product->name && $product->price) {
          $product->save();
          return $app->response(201, $product->toArray());
        } else {
          return 400;
        }
      });   

    });   
        
  });    
  
});

echo $app->run(new Bullet\Request());

在这里,使用了 simplexml_load_string() 方法将发送给 /v1/products.xml 的基于 XML 的 POST 对象转换为 PHP 对象,并且使用此对象的属性填充新的 Product 实例。保存之后,会将新创建的 Product 资源的 XML 表示返回给客户端。

下列图像显示了 XML POST 请求和响应的一个示例。

/v1/products.xml 的 POST 请求
/v1/products.xml 的 POST 请求

如果客户端想使用 JSON,那么可以将 JSON 请求正文发送给 URL /v1/products.json。在本例中,Bullet 自动将 JSON 请求正文解码为对象,此对象可以转换为 Product 实例并保存到数据库。客户端接收所保存 Product 资源的 JSON 表示,如下所示。如前所述,通过将 'Accept: application/json' 标头作为 POST 请求的一部分发送给 URL /v1/products,可以获得相同的结果。

/v1/products.json 的 POST 请求
/v1/products.json 的 POST 请求

步骤 7:处理错误并添加身份验证

BulletPHP 随附提供了一个强大的事件处理系统,该系统可以基于特定的服务器代码、响应格式或异常类型来执行自定义操作。为了说明这点,请考虑下一个清单,该清单使用事件处理程序自动捕获异常,并将它们作为用 JSON 编码的数据包发送给客户端。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// global 'Exception' handler event
$app->on('Exception', function($request, $response, Exception $e) use ($app) {
  // send 500 error with JSON info about exception
  $response->status(500);
  $response->content(array(
    'exception' => get_class($e),
    'message' => $e->getMessage()
  ));
});

// route handlers
// snipped

echo $app->run(new Bullet\Request());

下面是 API 如何响应异常的一个示例。

API 异常处理
API 异常处理

在处理请求之前或之后,还可以使用此事件处理系统执行操作或自定义事件。一种常见用法是身份验证:可以针对受保护的路由触发自定义的 'authentication required' 事件,然后为此事件编写一个处理程序,以检查发送请求的客户端是否具有访问这些路由所需的凭据。

下一个清单演示了一个简单的示例。在受保护的路由上触发了自定义 'auth' 事件;这将控制转交给了一个事件处理函数,它在进一步处理请求之前检查身份验证凭据(在本例中是 plaintext cookies)。

<?php
// set up Composer autoloader
require __DIR__ . '/vendor/autoload.php';

// initialize application
$app = new Bullet\App();

// runs when 'auth' event is triggered on protected API routes to check that
// credentials (here, plaintext cookies) are present
// replace with more complex authentication function in production
$app->on('auth', function($request, $response) use ($app) {

  if (!$request->cookie('uid') == 'demo' || !$request->cookie('pass') == 'demo') {
    $response->status(401);
    $response->send();
    exit;
  }
  
});

// unprotected API route to set authentication cookies
$app->path('set-auth-cookies', function($request) use ($app) {

  $app->get(function() use ($app)  {
    setcookie('uid', 'demo');
    setcookie('pass', 'demo');
    return 200;
  });
  
});

// protected API route
$app->path('v1', function($request) use ($app) {

  $app->path('products', function($request) use ($app) {
  
    // trigger authentication event
    $app->filter('auth');

    // route handler code
    // snipped
    }
}

echo $app->run(new Bullet\Request());

在此示例场景中,用户可以创建 cookies,方法是请求特殊的 /set-auth-cookies 路径;在生产环境中,用户可能需要通过注册来获得一个 API 密钥,并为每个请求提供它。

下面的图像显示了 API 如何使用 401 Unauthorized 代码响应未授权的请求。

未授权请求的 API 响应
未授权请求的 API 响应

步骤 8:部署到 Bluemix

现在已经完成了 API 的所有编码,最后一步是部署它。

  • 如果在本地部署,则已经完成了相应的操作。跳到本部分末尾,因为末尾部分列出了可与 API 交互的一些有用工具。
  • 如果在 Bluemix 上部署,则需要使用 Bluemix 帐户,并需要下载和安装 Cloud Foundry 命令行客户端。按照下面的步骤完成部署过程。

a. 创建应用程序清单

应用程序清单文件告诉 Bluemix 如何部署应用程序。具体地讲,它指定要使用的 PHP 运行时环境("构建包")。在 $APP_ROOT/manifest.yml 中创建此文件,使用下列信息填充它。

---
applications:
- name: products-api-[random-number]
memory: 256M
instances: 1
host: products-api-[random-number]
buildpack: https://github.com/dmikusa-pivotal/cf-php-build-pack.git

记住更新主机和应用程序名,让它们保持惟一,方法是更改它或为它添加一个随机数字。我使用的是 Cloud Foundry PHP 构建包,但还有其他选项可用。

b. 为 Bullet 设置 URL 路由

默认情况下,Cloud Foundry PHP 构建包使用 Apache 作为其 Web 服务器。Nginx 是一种轻型替代方法,在本步骤中,我们需要覆盖构建包的默认设置,以便使用 nginx 作为 Web 服务器。在开始之前,记住您可以从项目的 DevOps Services 源代码库获得此部分的所有文件。

首先,需要创建一个 $APP_ROOT/.bp-config 目录,然后使用下列内容创建 $APP_ROOT/.bp-config/options.json。

{
    "WEB_SERVER": "nginx"
}

此时,还必须为 nginx 设置 URL 重写规则,这样才能将 API 路径正确地传递给 Bullet 的 URL 路由器。首先,使用下列内容创建 $APP_ROOT/.bp-config/nginx/server-defaults.conf。

        listen @{VCAP_APP_PORT};
        server_name _;

        fastcgi_temp_path @{TMPDIR}/nginx_fastcgi 1 2;
        client_body_temp_path @{TMPDIR}/nginx_client_body 1 2;
        proxy_temp_path @{TMPDIR}/nginx_proxy 1 2;

        real_ip_header x-forwarded-for;
        set_real_ip_from 10.0.0.0/8;
        real_ip_recursive on;

        try_files $uri /index.php;

然后,使用下列内容创建 $APP_ROOT/.bp-config/nginx/server-locations.conf。

        # Some basic cache-control for static files to be sent to the browser
        location ~* \.(?:ico|css|js|gif|jpeg|jpg|png)$ {
            expires max;
            add_header Pragma public;
            add_header Cache-Control "public, must-revalidate, proxy-revalidate";
        }

        # Deny hidden files (.htaccess, .htpasswd, .DS_Store).
        location ~ /\. {
            deny all;
            access_log off;
            log_not_found off;
        }

        # pass .php files to fastcgi
        location ~ .*\.php$ {
            try_files $uri =404;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_pass php_fpm;
        }

c. 连接到 Bluemix 并部署应用程序

凭借 'cf' 命令行工具使用 IBM id 和密码登录到 Bluemix。

shell> cf api https://api.ng.Bluemix.net
shell> cf login

更改到 $APP_ROOT 目录并将应用程序推送到 Bluemix。

shell> cf push

下面是您在此过程中看到的内容示例。

将应用程序部署到 Bluemix
将应用程序部署到 Bluemix

此时,您的应用程序应该已经部署到了 Bluemix。但是还没有完成所有操作!

d. 将 MySQL 服务实例绑定到应用程序

现在已经部署了应用程序,但您仍然需要将它与 MySQL 数据库实例连接起来,这样您的 API 才有一些数据可供使用。为此,访问 Bluemix 管理仪表板并使用您的 id 和密码登录。您应该会看到 'Apps' 菜单栏列出了您的应用程序。

Bluemix 应用程序仪表板
Bluemix 应用程序仪表板

选择您的应用程序,在生成的页面上,使用 'Add new service' 选项将 'mysql' 服务添加到您的应用程序。

在 Bluemix 上将 MySQL 服务绑定到应用程序
在 Bluemix 上将 MySQL 服务绑定到应用程序

您现在应在 Bluemix 管理仪表板中看到 MySQL 服务实例已经绑定到了应用程序。

MySQL 服务实例已经绑定到了 Bluemix 应用程序
MySQL 服务实例已经绑定到了 Bluemix 应用程序

具体地讲,如果您查看应用程序细节,应该能够在 VCAP_SERVICES 环境变量中看到 MySQL 访问凭据。

Bluemix 应用程序环境变量
Bluemix 应用程序环境变量

e. 安装示例模式

此时,可以使用如步骤 1 所示的示例模式初始化应用程序数据库。尽管在 Bluemix 环境中没有办法直接运行步骤 1 中的 SQL 命令,但应用程序包含一个名为 /install-schema 的特殊路径,可以通过浏览器请求它,以便设置数据库表和示例数据。尽管本文没有记录此路径,但可以在应用程序的源代码库中找到它。

下面的图像显示了通过 /install-schema 路径成功安装模式的结果。

6. 开始使用 REST API
Bluemix 上的模式安装
Bluemix 上的模式安装

部署 REST API 之后,可以向它发送 GET、POST、PUT 和 DELETE 请求。

  • Firefox 的 HttpRequester 和 Chrome 的 Postman 扩展是通过浏览器构建和发送不同 API 请求类型的强大工具。本文中的大部分屏幕截图都是使用 Postman 扩展生成的。
  • 可以使用任意流行的客户端或服务器端编程语言向 API 提交请求:jQuery、PHP、Perl、Java 和 Python(以及其他语言)都支持 HTTP 请求提交和响应处理。
  • 本文还包含使用 Swagger 的生动 API 演示,Swagger 为 API 探索提供了一个基于浏览器的工具。可以使用此演示添加和更新产品,删除产品或检索各个产品。下面的图像显示了实际的 Swagger 前端。Swagger API 浏览器
    Swagger API 浏览器

结束语

如本文所述,Bluemix 为在基于云的平台上创建和部署 REST API 奠定了坚实的基础。通过添加针对常见 REST 协定的 Bullet 灵活支持和 Eloquent 非常直观的 ORM,您可以获得快速制作原型和部署自定义 REST API 所需的一切内容。

您可以从本文的 Devops Services 存储库下载本文实现的所有代码和所用 PHP 构建包的配置文件。建议您获取这些代码并运行它们,然后添加新的功能。我可以保证您不会有任何损失,而且可以学到更多的东西。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Cloud computing
ArticleID=972663
ArticleTitle=在 Bluemix 上使用 PHP 和 MySQL 构建和部署 REST API
publish-date=05292014