Содержание


Создание службы хранения фотографий в облаке с помощью PHP и IBM Bluemix. Часть 2

Развертывание приложения для хранения фотографий в Bluemix

Comments

Все больше и больше приложений используют дешевые, надежные службы хранения объектов в облаке. Вы тоже можете добавить к своему приложению облачное хранилище, воспользовавшись службой IBM® Bluemix™ Object Storage. В этой серии руководств показано, как построить службу хранения фотографий в облаке с помощью IBM Bluemix и PHP. Для создания приложения используется микросреда PHP Silex, для поддержки нескольких пользователей – библиотека аутентификации HybridAuth, а для получения гибкого, дружественного к мобильным устройствам пользовательского интерфейса – построитель шаблонов Bootstrap.

В первой части мы показали, как создать приложение-заготовку, организовать пользовательский интерфейс, обеспечить возможность добавления фотографий и начать работать с ними. В Части 2 мы развернем приложение в IBM Bluemix и добавим поддержку нескольких контейнеров и нескольких пользователей.

Запустить приложениеПолучить код

Шаг 1. Развертывание в Bluemix

Для того чтобы развернуть приложение в Bluemix, нужны учетная запись Bluemix и инструмент командной строки Cloud Foundry.

  1. Создание манифеста приложения

    Файл манифеста приложения указывает Bluemix, как развертывать приложение. В частности, он указывает среде выполнения PHP («пакету сборки»), что следует использовать. Создайте этот файл под именем $APP_ROOT/manifest.yml и заполните его следующей информацией.

    --- 
    applications: 
    - name: photos-[your-initials] 
    memory: 256M 
    instances: 1 
    host: photos-[your-initials] 
    buildpack: https://github.com/dmikusa-pivotal/cf-php-build-pack.git

    Не забудьте отредактировать имена хоста и приложения, чтобы сделать их уникальными, – например, измените их или добавьте произвольное число. Имя приложения должно быть уникальным в пределах вашей организации и пространства Bluemix, а имя хоста – уникальным для всей среды Bluemix. Я использую пакет сборки PHP CloudFoundry, хотя есть и другие варианты.

  2. Настройка пакета сборки PHP

    По умолчанию в пакет сборки PHP Cloud Foundry входят некоторые наиболее распространенные расширения PHP. Расширение PHP fileinfo по умолчанию не включено, но оно необходимо для проверки MIME-типа загруженного файла. Это расширение довольно легко добавить в процессе разработки, отредактировав пакет сборки.

    Чтобы добавить поддержку расширения PHP fileinfo, создайте каталог $APP_ROOT/.bp-config, а затем файл $APP_ROOT/.bp-config/options.json со следующим содержимым:

    { 
        "WEB_SERVER": "httpd", 
        "PHP_EXTENSIONS": ["bz2", "zlib", "curl", "mcrypt", "fileinfo"] 
    }

    По умолчанию веб-сервер Apache, включенный в пакет сборки, уже содержит включенную поддержку mod_rewrite и .htaccess, так что для включения функций перезаписи URL Silex вам больше не нужно ничего делать.

  3. Подключение к Bluemix и развертывание приложения

    Войдите в Bluemix с именем пользователя и паролем IBM, используя интерфейс командной строки cf:

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

    Перейдите в каталог $APP_ROOT и передайте приложение в Bluemix: shell> cf push.

    Вот пример того, что вы увидите в ходе этого процесса:

    Передача приложения в Bluemix
    Передача приложения в Bluemix
  4. Привязка к приложению службы Object Storage

    Теперь приложение развернуто, но к нему еще нужно подключить экземпляр Object Storage, чтобы где-то хранить загруженные файлы. Откройте панель администрирования Bluemix и войдите со своим именем пользователя и паролем IBM. Ваше приложение отображается на панели управления:

    Приложение на панели управления
    Приложение на панели управления

    Выберите свое приложение и нажмите кнопку Add a service or API, чтобы добавить к приложению службу Object Storage (в категории Data Management):

    Окно добавления службы
    Окно добавления службы

    Перезапустите приложение. На панели управления Bluemix видно, что к приложению привязан экземпляр службы Object Storage:

    Экземпляр службы Object Storage
    Экземпляр службы Object Storage

    Проверив переменные среды своего приложения на панели управления Bluemix, вы также увидите URI своего экземпляра Object Storage в переменной среды VCAP_SERVICES. Эта информация извлекается автоматически и используется приложением, как говорилось выше:

    Переменные среды
    Переменные среды
  5. Начало работы с приложением

    Получите доступ к своему приложению, набрав в веб-браузере имя хоста, указанное в манифесте приложения – например, http://photos-[ваши инициалы].mybluemix.net. Попробуйте загрузить несколько файлов через интерфейс:

    Фотографии, перечисленные в интерфейсе
    Фотографии, перечисленные в интерфейсе

    Если вы видите пустую страницу или другие ошибки, используйте ссылку, приведенную в начале этого раздела, для отладки PHP-кода и выяснения причины неполадок.

На данный момент приложение полностью работает, хотя и с двумя важными ограничениями: оно поддерживает только один контейнер (по умолчанию) и одну учетную запись пользователя (me). Те, кому нужно решение для хранения данных своих личных фотографий в облаке без всяких излишеств, могут остановиться прямо здесь. Если же вы хотите воспользоваться всеми преимуществами облака, которые дает служба Bluemix Object Storage, выполните шаги 2 и 3, чтобы добавить поддержку нескольких пользователей и нескольких контейнеров на каждого пользователя. На шаге 4 приведен код, необходимый для добавления аутентификации пользователей.

Шаг 2. Добавление поддержки нескольких контейнеров

В клиентской библиотеке PHP есть метод listContainers(), который возвращает список всех доступных контейнеров, связанных с учетной подзаписью. Сначала используем его, чтобы отредактировать форму загрузки фотографий и позволить пользователю выбрать контейнер, в который будут сохраняться загруженные файлы. Добавим поле для ввода произвольного текста, чтобы пользователь мог создавать новые контейнеры.

Вот отредактированный шаблон формы с изменениями, выделенными жирным шрифтом, который нужно сохранить в файле $APP_ROOT/views/add.twig:

<!DOCTYPE html> 
<html lang="en"> 
  <head> 
    <meta charset="utf-8"> 
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> 

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> 
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> 
    <!--[if lt IE 9]> 
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> 
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> 
    <![endif]--> 
  </head> 
  <body> 
    <div class="panel panel-default"> 
      <div class="panel-heading clearfix"> 
        <h4 class="pull-left">Add Photo</h4> 
      </div> 
    </div> 

    <form enctype="multipart/form-data" action="{{ app.request.basepath }}/add" method="post"> 
      <div class="form-group"> 
        <div class="input-group"> 
          <span class="input-group-addon">Select photo</span> 
          <input type="file" name="file" class="form-control" style="height: auto" /> 
        </div> 
      </div> 
 
      <div class="form-group"> 
        <div class="input-group"> 
          <span class="input-group-addon">Select container</span> 
          <select class="form-control" name="container"> 
            {% for c in containers %} 
            <option value="{{ c.name|url_encode }}">{{ c.name }}</option> 
            {% endfor %} 
          </select> 
        </div> 
      </div> 
 
      <div class="form-group"> 
        <div class="input-group"> 
          <span class="input-group-addon">Use new container</span> 
          <input type="text" name="container-new" class="form-control" /> 
        </div> 
      </div> 

      <div class="form-group"> 
        <button type="submit" name="submit" class="btn btn-default">Upload</button> 
      </div> 

    </form> 
 
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> 
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>    
  </body> 
</html>

А вот отредактированные обработчики GET и POST для маршрута /add, который выводит на экран список доступных контейнеров, создает новые контейнеры с помощью метода createContainer() и связывает загруженные файлы с контейнерами с помощью метода setObject():

<?php 
// Инициализация и настройка приложения – в сокращении! 
 
// Форма загрузки файлов 
$app->get('/add', function () use ($app) { 
  $containers = (array) $app['os']->listContainers(); 
  return $app['twig']->render('add.twig', array('containers' => $containers)); 
}); 
 
// Процессор загрузки файлов 
// Получение и проверка загруженного файла 
// Создание контейнера и добавление файла, если все правильно 
$app->post('/add', function (Request $request) use ($app) { 
  $file = $request->files->get('file'); 
  $containerNew = $request->get('container-new'); 
  $containerExisting = $request->get('container'); 
  $container = (!empty($containerNew)) ? urldecode($containerNew) : urldecode($containerExisting); 
  if ($file && $file->isValid()) { 
    if (in_array($file->getClientMimeType(), array('image/gif', 'image/jpeg', 'image/png'))) { 
      if (!empty($containerNew)) { 
        $app['os']->createContainer($container); 
      } 
      $app['os']->setObject($container, $file->getClientOriginalName(), file_get_contents($file->getRealPath())); 
    } else { 
      throw new Exception('Invalid image format'); 
    } 
  } else { 
    throw new Exception('Invalid upload'); 
  } 
  return $app->redirect($app["url_generator"]->generate('index'));     
}); 
 
// Обработчики маршрутов – в сокращении! 
 
$app->run();

Аналогично, в шаблон страницы index необходимо добавить поддержку вывода списка и удаления нескольких контейнеров. Вот версия файла $APP_ROOT/views/index.twig:

<!DOCTYPE html> 
<html lang="en"> 
  <head> 
    <meta charset="utf-8"> 
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> 
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> 
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> 
    <!--[if lt IE 9]> 
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> 
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> 
    <![endif]--> 
  </head> 
  <body> 
 
    <div class="panel panel-default"> 
      <div class="panel-heading clearfix"> 
        <h4 class="pull-left">Photos</h4> 
      </div> 
    </div> 

    {% if containers|length > 0 %} 
    {% for c in containers %} 
    <div class="container"> 
      <p class="clearfix"> 
          <span class="btn btn-success"> {{ c.name }} <span class="badge">{{ c.objects|length }}</span></span> 
      </p> 
      <div class="clearfix"> 
          <a href="{{ app.request.basepath }}/add" role="button" class="btn btn-primary btn-sm"><span class="glyphicon glyphicon-plus"></span> Add Photo</a> 
          <a href="{{ app.request.basepath }}/delete/{{ c.name|url_encode }}" role="button" class="btn btn-primary btn-sm"><span class="glyphicon glyphicon-exclamation-sign"></span> Delete Container</a> 
      </div> 
      <hr/> 
      {% for o in c.objects %}         
      <ul class="list-group row clearfix"> 
        <li class="list-group-item col-xs-3 clearfix" style="border:none"><img src="{{ o.url }}" class="img-responsive" /></li> 
        <li class="list-group-item col-xs-5 clearfix" style="border:none"> 
          <p> {{ o.name }} </p> 
          <h6> {{ o.bytes }} bytes </h6> 
        </li> 
        <li class="list-group-item col-xs-4 clearfix" style="border:none"> 
          <p> 
            <a href="{{ o.url }}" role="button" class="btn btn-primary btn-sm">View</a> <br/> 
          </p> 
          <p> 
            <a href="{{ app.request.basepath }}/delete/{{ c.name|url_encode }}/{{ o.name }}" role="button" class="btn btn-primary btn-sm">Delete</a> 
          </p>           
        </li> 
      </ul> 
      <hr/> 
      {% endfor %} 
    </div> 
    {% endfor %} 
    {% else %} 
    <div class="container"> 
      <h4>No photos found.</h4> 
      <a href="{{ app.request.basepath }}/add" role="button" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-plus"></span> Add</a> 
    </div> 
    {% endif %} 

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> 
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>    
  </body> 
</html>

Теперь обработчик страницы index также должен использовать метод listContainers() для извлечения всех доступных контейнеров, прежде чем перебирать их индивидуально:

<?php 
// Инициализация и настройка приложения – в сокращении! 
 
// Обработчик страницы index  
$app->get('/index', function () use ($app) { 
  $containers = (array) $app['os']->listContainers(); 
  foreach ($containers as &$c) { 
    $objects = json_decode($app['os']->listObjects($c['name'])); 
    foreach ($objects as &$o) { 
      $o = (array) $o; 
      $o['url'] = $app['os']->getObjectUrl($c['name'], $o['name']); 
    } 
    $c['objects'] = $objects; 
  } 
  return $app['twig']->render('index.twig', array( 
    'containers' => $containers 
    )); 
})->bind('index'); 
 
// Обработчики маршрутов – в сокращении! 
 
$app->run();

Наконец, обработчик /delete нужно настроить на поддержку удаления контейнера (и объекта):

<?php 
// Инициализация и настройка приложения – в сокращении! 
 
// Обработчик delete  
// Если есть объект, удалить его 
// Если есть контейнер, удалить объекты из контейнера, затем удалить контейнер 
$app->get('/delete/{container}/{object}', function ($container, $object) use ($app) { 
  $container = urldecode($container); 
  $object = urldecode($object); 
  if (empty($object)) { 
    $objects = json_decode($app['os']->listObjects($container)); 
    foreach ($objects as $o) { 
      $app['os']->deleteObject($container, $o->name);   
    } 
    $app['os']->deleteContainer($container);     
  } else { 
    $app['os']->deleteObject($container, $object);   
  } 
  return $app->redirect($app["url_generator"]->generate('index')); 
}) 
->value('object', ''); 
 
$app->error(function (\Exception $e, $code) use ($app) { 
  return $app['twig']->render('error.twig', array('error' => $e->getMessage())); 
}) 
->value('object', ''); 
 
// Обработчики маршрутов – в сокращении! 
 
$app->run();

Здесь обработчик маршрута /delete проверяет, содержат ли параметры маршрута имя контейнера и имя объекта или только имя контейнера. В последнем случае для удаления только указанного объекта, как и прежде, используется метод deleteObject(). В первом же случае каждый объект из указанного контейнера удаляется отдельно с помощью метода deleteObject(), а затем, когда контейнер пуст, удаляется сам контейнер с помощью метода deleteContainer().

Если перенести отредактированный код в Bluemix, то при загрузке фотографий появится возможность создавать новые (или выбирать существующие) контейнеры и выводить на экран список фотографий, организованный по контейнерам:

Список фотографий
Список фотографий

Шаг 3. Добавление поддержки нескольких пользователей

Что касается добавления поддержки нескольких пользователей, то служба Bluemix Bluemix Object позволяет сделать это довольно легко. Поскольку она имеет свою собственную систему проверки подлинности и каждый экземпляр службы может поддерживать несколько учетных подзаписей, поддержка нескольких пользователей сводится к созданию отдельной подзаписи для каждого пользователя экземпляра и использованию этой подзаписи для всех последующих операций с контейнерами и объектами, связанными с пользователем. Конечно, все равно понадобится уровень аутентификации на стороне клиента, чтобы пользователи не могли получать доступ к подзаписям друг друга... вот здесь-то и может помочь HybridAuth.

HybridAuth – это PHP-библиотека с открытым исходным кодом, которая позволяет проверять подлинность пользователей через широкий круг социальных сетей и служб, в том числе Facebook, Google+, LinkedIn и Twitter. HybridAuth абстрагирует от деталей служб аутентификации и авторизации, облегчая добавление входа в PHP-приложение через социальную сеть без сложностей, связанных с операциями OAuth и маркерами доступа. Здесь HybridAuth используется для реализации уровня аутентификации, который требует от пользователя войти через внешнюю службу и создает или предоставляет доступ к его учетной подзаписи только в случае успешной проверки.

Чтобы приступить к использованию библиотеки HybridAuth, добавьте ее в свой проект, обновив файл $APP_ROOT/composer.json:

{ 
    "require": { 
        "silex/silex": "*", 
        "twig/twig": "*",         
        "ibmjstart/zendservice-openstack": "dev-master", 
        "hybridauth/hybridauth": "2.*" 
    }, 
    "minimum-stability" : "dev", 
    "prefer-stable": true 
}

Установка HybridAuth с помощью Composer:

shell> cd /usr/local/apache/htdocs/photos 
shell> php composer.phar update

Следующий шаг – решить, какую социальную сеть использовать для проверки подлинности. Для простоты я использую Google+, хотя так же легко использовать Twitter, Facebook, MySpace, LinkedIn или Foursquare (или же все эти сети вместе). Сначала нужно зарегистрировать веб-приложение в Google и предоставить ему доступ к API Google+ для проверки подлинности.

Войдите в Google с помощью своих реквизитов доступа Google и откройте Google Developers Console. Создайте новый проект, дайте ему имя, а затем включите доступ к API Google+ в разделе APIs проекта:

Раздел API Google
Раздел API Google

Получите ID клиента OAuth 2.0 и секретный ключ для своего приложения в разделе Credentials проекта. Запишите эти значения, так как они понадобятся для HybridAuth:

ID клиента и секретный ключ
ID клиента и секретный ключ

При получении ID клиента OAuth и секретного ключа задайте перенаправление URL-адреса приложения. Это URL-адрес, по которому Google будет перенаправлять браузер клиента после завершения процесса аутентификации OAuth. Для этого примера задайте URL-адрес своего домена Bluemix, добавив к нему специальный путь /callback?hauth.done=Google – например, http://photos-[ваши инициалы].mybluemix.net/callback?hauth.done=Google. Этот специальный маршрут /callback будет определен в приложении позднее.

Примечание. Не забудьте прочесть условия Google APIs Terms of Service, Google+ Platform Developer Policies, Google+Platform Terms of Service и Google Privacy Policies, чтобы убедиться, что ваше приложение полностью им соответствует. Например, нужно предоставить пользователям возможность в любой момент удалить все свои данные из вашей системы. Пример необходимого для этого кода приведен в репозитории кода приложения.

Шаг 4. Добавление кода проверки подлинности пользователя

Выполнив всю подготовительную работу, займемся написанием кода. Начнем с настройки HybridAuth в приложении, добавив в файл $APP_ROOT/index.php следующие строки:

<?php 
// Инициализация приложения – в сокращении! 
 
// Загрузка файла конфигурации с секретным ключом OAuth 
require 'config.php'; 
 
// Определение массива конфигурации 
// ... службы Object Storage 
$config["storage"] = array( 
  'service' => array( 
  ), 
  'adapter' => array( 
    'adapter' => 'Zend\Http\Client\Adapter\Curl', 
    'curloptions' => array(CURLOPT_SSL_VERIFYPEER => false, CURLOPT_TIMEOUT => 6000),   
  ) 
); 
 
// ... к HybridAuth 
$config["hybridauth"]  = array( 
  "base_url" => "http://photos.mybluemix.net/callback", 
  "providers" => array ( 
  "Google" => array ( 
    "enabled" => true, 
    "keys" => array ( 
      "id" => $oauth_id, 
      "secret" => $oauth_secret 
    ), 
    "scope" => "https://www.googleapis.com/auth/userinfo.email" 
))); 
 
// Использование среды BlueMix VCAP_SERVICES 
if ($services = getenv("VCAP_SERVICES")) { 
  $services_json = json_decode($services, true); 
  $config["storage"]["service"]["url"] = $services_json["objectstorage"][0]["credentials"]["auth_uri"]; 
  $config["storage"]["service"]["user"] = $services_json["objectstorage"][0]["credentials"]["username"]; 
  $config["storage"]["service"]["key"] = $services_json["objectstorage"][0]["credentials"]["password"]; 
} else { 
  throw new Exception('Not in Bluemix environment'); 
} 
 
// Запуск сеанса 
session_start(); 
 
// Инициализация клиента HybridAuth 
$auth = new Hybrid_Auth($config["hybridauth"]); 
 
// Инициализация приложения Silex 
$app = new Application(); 
 
// Обработчики маршрутов – в сокращении! 
 
$app->run();

Приведенный выше код инициализирует новый клиент HybridAuth, доступный через объект $auth. ID клиента и секретный ключ OAuth для вашего приложения уже были созданы на предыдущем шаге; теперь они загружаются из файла конфигурации, который выглядит следующим образом:

<?php 
// config.php 
$oauth_id = "YOUR-ID-HERE"; 
$oauth_secret = "YOUR-SECRET-HERE";

Ясно, что указанные выше значения нужно заменить на правильные для вашей установки.

Далее, добавим некоторые маршруты для входа и выхода вместе со специальным маршрутом /callback, необходимым для OAuth-аутентификации:

<?php 
// Инициализация и настройка приложения – в сокращении! 
 
// Обработчики маршрутов – в сокращении! 
 
// Обработчик входа 
// Проверка, выполнена ли аутентификация у поставщика 
// Получение адреса электронной почты пользователя и его сохранение а сеансе 
$app->get('/login', function () use ($app, $auth) { 
  $google = $auth->authenticate("Google"); 
  $currentUser = $google->getUserProfile(); 
  $_SESSION['uid'] = $currentUser->email; 
  return $app->redirect($app["url_generator"]->generate('index')); 
}) 
->bind('login'); 
 
// Обработчик выхода 
// Выход и отображение информационной страницы logout  
$app->get('/logout', function () use ($app, $auth) { 
  $auth->logoutAllProviders(); 
  session_destroy(); 
  return $app['twig']->render('logout.twig'); 
}); 
// Обработчик обратного вызова OAuth 
$app->get('/callback', function () { 
  return Hybrid_Endpoint::process(); 
}); 
 
$app->run();

Ниже приводится краткий обзор внесенных изменений.

  • Обработчик /login использует метод authenticate() клиента HybridAuth для проверки подлинности пользователя с помощью API Google+. Как это делается, не важно – метод authenticate() абстрагирует от всех сложностей процессов аутентификации и авторизации OAuth. После проверки подлинности метод getUserProfile() результирующего объекта возвращает профиль авторизованного пользователя в структурированном формате. Этот профиль включает в себя имя пользователя, URL-адрес профиля, URL-адрес фотографии и другую личную информацию, но для целей данного приложения все, что вам действительно нужно, – это адрес электронной почты, связанный с пользователем, который будет служить его уникальным идентификатором. Этот идентификатор сохраняется в переменной сеанса $_SESSION['uid'], и клиент направляется на страницу index приложения.
  • Для логики OAuth-аутентификации требуется обработчик /callback, так как это URL-адрес, к которому Google будет перенаправлять браузер клиента после завершения процесса аутентификации. На самом деле вам не нужно беспокоиться и об этом, так как метод Hybrid_Endpoint::process() абстрагирует работу по обработке обратного вызова.
  • Обработчик /logout использует метод logoutAllProviders() объекта HybridAuth для выхода из всех подключенных служб. Он также прерывает текущий сеанс приложения и отображает следующий шаблон страницы, который нужно сохранить как файл $APP_ROOT/views/logout.twig.
<!DOCTYPE html> 
<html lang="en"> 
  <head> 
    <meta charset="utf-8"> 
    <meta http-equiv="X-UA-Compatible" content="IE=edge"> 
    <meta name="viewport" content="width=device-width, initial-scale=1"> 
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> 

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> 
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> 
    <!--[if lt IE 9]> 
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> 
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> 
    <![endif]--> 
  </head> 
  <body> 
    <div class="panel panel-default"> 
      <div class="panel-heading clearfix"> 
        <h4 class="pull-left">Log out</h4> 
      </div> 
    </div>     
    <div class="container"> 
      You are now signed out of the application. However, since you originally signed in using your Google Account, you must also sign out of your Google Account to completely destroy your session. 
      <br/> 
      <a href="{{ app.request.basepath }}/index" role="button" class="btn btn-default">Back</a> 
      <a href="https://accounts.google.com/Logout" role="button" class="btn btn-default">Sign out of Google</a> 
    </div> 
 
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> 
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> 
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>    
  </body> 
</html>

Как видно из приведенного выше шаблона страницы, при выходе из приложения сеанс Google OAuth остается активным. Поэтому страница выхода предлагает пользователю дополнительную возможность выйти из подключенной учетной записи Google и полностью прекратить действие маркера и сеанс OAuth.

Поскольку переменная $_SESSION['uid'] существует только в том случае, если пользователь успешно прошел аутентификацию, ее можно использовать для защиты доступа к другим маршрутам приложения:

<?php 
// Регистрация промежуточного ПО аутентификации 
$authenticate = function (Request $request, Application $app) use ($config) { 
  if (!isset($_SESSION['uid'])) { 
    return $app->redirect($app["url_generator"]->generate('login')); 
  } 
  $config["storage"]["service"]["url"] .= '/' . str_replace(array('@', '.'), '_', $_SESSION['uid']); 
  $app['os'] = new ObjectStorage( 
    $config["storage"]["service"], 
    new Zend\Http\Client('', $config["storage"]["adapter"]) 
  );     
};

Приведенная выше функция $authenticate проверяет наличие идентификатора пользователя в сеансе. Если его нет, пользователь перенаправляется на URL /login для повторного входа. Если идентификатор присутствует, то при каждом запросе к URL-адресу аутентификации Object Storage автоматически добавляется адрес электронной почты пользователя в качестве имени учетной подзаписи. Следовательно, каждая операция — вывод списка контейнеров, добавление или удаление фотографий — будет выполняться только в этой подзаписи, что позволяет нескольким пользователям хранить свои фотографии в службе, не мешая друг другу.

Функция $authenticate используется в качестве промежуточного ПО Silex: она выполняется автоматически перед обработкой запроса путем ее присоединения к соответствующему обработчику маршрута с помощью метода before(). Это позволяет ограничить доступ, так что только аутентифицированные пользователи могут обращаться по этим маршрутам приложения. Если вы посмотрите на исходный код приложения, то увидите это промежуточное ПО в составе обработчиков /index, /save, /delete и /logout.

Чтобы увидеть его в действии, попробуйте, как раньше, зайти по URL-адресу своего приложения http://photos-[ваши инициалы].mybluemix.net. На этот раз, вместо того чтобы сразу увидеть список контактов, вы получите предложение войти в свою учетную запись Google. После этого откроется экран авторизации OAuth:

Экран авторизации
Экран авторизации

Вы попадете на главную страницу приложения только после того, как получите доступ к своему профилю, как на показанном выше экране. Обратите внимание, что первый вход в новую учетную запись может занять несколько минут, так как в процессе подготовки новой учетной подзаписи служба Bluemix Object Storage должна загрузить главную страницу приложения.

Заключение

Это руководство посвящено службе Bluemix Object Storage, которая облегчает добавление надежного, масштабируемого облачного хранилища для настольного или мобильного веб-приложения. Объединив эту службу с платформой Bluemix, написав небольшой код PHP и оптимизировав пользовательский интерфейс для мобильных устройств, разработчики могут быстро и эффективно создавать мощные приложения для конечных пользователей и организаций, позволяющие хранить файлы и управлять ими в облаке с любого устройства.

Если вы хотите поэкспериментировать со службой Bluemix Object Storage, начните с демонстрации действующего приложения. Затем загрузите код и рассмотрите его, чтобы понять, как все это работает вместе. Вы можете также обращаться по ссылкам, приведенным в верхней части каждого раздела, чтобы узнать больше об API Bluemix Object Storage, микросреде Silex и других инструментах и методах, используемых в этом руководстве. Успешного программирования!


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Облачные вычисления
ArticleID=1030531
ArticleTitle=Создание службы хранения фотографий в облаке с помощью PHP и IBM Bluemix. Часть 2
publish-date=04252016