Какой хостинг выбрать для сервера rest api
Перейти к содержимому

Какой хостинг выбрать для сервера rest api

  • автор:

RESTful API для сервера – делаем правильно (Часть 1)

В 2007-м Стив Джобс представил iPhone, который произвел революцию в высокотехнологичной индустрии и изменил наш подход к работе и ведению бизнеса. Сейчас 2012-й и все больше и больше сайтов предлагают нативные iOS и Android клиенты для своих сервисов. Между тем не все стартапы обладают финансами для разработки приложений в дополнение к основному продукту. Для увеличения популярности своего продукта эти компании предлагают открытые API, которыми могут воспользоваться сторонние разработчики. Пожалуй Twitter был первым в этой сфере и теперь число компаний, последовавших этой стратегии, растет стремительно. Это действительно отличный способ создать привлекательную экосистему вокруг своего продукта.

Жизнь стартапа полна перемен, поворотных моментов, в которых от принятых решений зависит дальнейшая судьба проекта. Если ваша кодовая база не сможет обеспечить воплощение самых разных ваших решений – вы проиграли. Серверный код, который достаточно гибок для того, чтобы в короткие сроки подстроиться под нужды бизнеса, решает быть проекту или нет. Успешные стартапы не те, которые просто предложили отличную идею, но те, которые смогли ее качественно воплотить. Успех стартапа зависит от успешности его продукта, будь то приложение под iOS, сервис или API. Последние три года я работал над разными приложениями под iOS (в основном для стартапов) использовавшими web сервисы и в этом блоге я попытался собрать накопленные знания воедино и показать вам лучшие методики, которым вам нужно следовать при разработке RESTful API. Хороший RESTful API тот, который можно менять легко и просто.

Целевая аудитория

Этот пост предназначен для тех, кто обладает знаниями в разработке RESTful API уровня от средних до продвинутых. А также некоторыми базовыми знаниями объектно-ориентированного (или функционального) программирования на таких серверных языках как Java/Ruby/Scala. (Я намеренно проигнорировал PHP или Programmable Hyperlinked Pasta).
Прим. Пер. Тут автор привел ссылку на полушутливую статью о истории языков программирования где PHP был расшифрован как Programmable Hyperlinked Pasta (Программируемая Гиперссылочная Лапша). Что как бы характеризует отношение автора к PHP.

Структура и организация статьи

Статья довольно подробна и состоит из двух частей. Первая описывает основы REST тогда как вторая описывает документирование и поддержку разных версий вашего API. Первая часть для новичков, вторая для профи. Я не сомневаюсь, что вы профи, а потому вот вам ссылка чтобы перескочить сразу к главе «Документирование API». Возможно, вам стоит начать оттуда, если вам кажется, что этот пост из разряда «Многа букаф, ниасилил…».

Принципы RESTful

Сервер может считаться RESTful если он соответствует принципам REST. Когда вы разрабатываете API, который будет в основном использоваться мобильными устройствами, понимание и следование трем наиважнейшим принципам может быть весьма полезным. Причем не только при разработке API, но и при его поддержке и развитии в дальнейшем. Итак, приступим.

Независимость от состояния (Statelessness)

Первый принцип – независимость от состояния. Проще говоря, RESTful сервер не должен отслеживать, хранить и тем более использовать в работе текущую контекстную информацию о клиенте. С другой стороны клиент должен взять эту задачу на себя. Другими словами не заставляйте сервер помнить состояние мобильного устройства, использующего API.

Давайте представим, что у вас есть стартап под названием «Новый Фейсбук». Хороший пример, где разработчик мог совершить ошибку это предоставление вызова API, который позволяет мобильному устройству установить последний прочитанный элемент в потоке (назовем его лентой Фейсбука). Вызов API, обычно возвращающий ленту (назовем его /feed), теперь будет возвращать элементы, которые новее установленного. Звучит умно, не правда ли? Вы «оптимизировали» обмен данными между клиентом и сервером? А вот и нет.

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

Независимость от состояния означает, что данные, возвращаемые определенным вызовом API, не должны зависеть от вызовов, сделанных ранее.

Правильный способ оптимизации данного вызова – передача времени создания последней прочитанной записи ленты в качестве параметра вызова API, возвращающего ленту (/feed?lastFeed=20120228). Есть и другой, более «правильный» метод – использование заголовка HTTP If-Modified-Since. Но мы пока не будем углубляться в эту сторону. Мы обсудим это во второй части.

Клиент же со своей стороны, может (должен) помнить параметры, сгенерированные на сервере при обращении к нему и использовать их для последующих вызовов API, если потребуется.

Кэшируемая и многоуровневая архитектура

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

Клиент – серверное разделение и единый интерфейс

RESTful сервер должен прятать от клиента как можно больше деталей своей реализации. Клиенту не следует знать о том, какая СУБД используется на сервере или сколько серверов в данный момент обрабатывают запросы и прочие подобные вещи. Организация правильного разделения функций важна для масштабирования если ваш проект начнет быстро набирать популярность.

Это пожалуй три самых важных принципа, которым нужно следовать в ходе разработки RESTful сервера. Далее будут описаны три менее важных принципа, но все они имеют непосредственное отношение к тому, о чем мы тут говорим.

REST запросы и четыре HTTP метода

GET
POST
PUT
DELETE

Принцип “кэшируемости” и GET запросы

Главное, что следует помнить — вызов, совершенный через GET не должен менять состояние сервера. Это в свою очередь значит, что ваши запросы могут кэшироваться любым промежуточным прокси (снижение нагрузки). Таким образом Вы, как разработчик сервера, не должны публиковать GET методы, которые меняют данные в вашей базе данных. Это нарушает философию RESTful, особенно второй пункт, описанный выше. Ваши GET вызовы не должны даже оставлять записей в access.log или обновлять данные типа “Last logged in”. Если вы меняете данные в базе, это обязательно должны быть методы POST/PUT.

То самое обсуждение POST vs PUT

Спецификация HTTP 1.1 гласит, что PUT идемпотентен. Это значит, что клиент может выполнить множество PUT запросов по одному URI и это не приведет к созданию записей дубликатов. Операции присвоения — хороший пример идемпотентной операции

String userId = this.request["USER_ID"]; 

Даже если эту операцию выполнить дважды или трижды, никакого вреда не будет (кроме лишних тактов процессора). POST же с другой стороны не идемпотентен. Это что-то вроде инкремента. Вам следует использовать POST или PUT с учетом того является ли выполняемое действие идемпотентным или нет. Говоря языком программистов, если клиент знает URL объекта, который нужно создать, используйте PUT. Если клиент знает URL метода/класса создающего нужный объект, используйте POST.

PUT www.example.com/post/1234 

Используйте PUT если клиент знает URI, который сам бы мог быть результатом запроса. Даже если клиент вызовет это PUT метод много раз, какого либо вреда или дублирующих записей создано не будет.

POST www.example.com/createpost 

Используйте POST если сервер сам создает уникальный идентификатор и возвращает его клиенту. Дублирующие записи будут создаваться если этот запрос будет повторяться позже с такими же параметрами.
Более подробная информация в данном обсуждении.

Метод DELETE

DELETE абсолютно однозначен. Он идемпотентен как и PUT, и должен использоваться для удаления записи если таковая существует.

REST ответы

Ответы от Вашего RESTful сервера могут использовать в качестве формата XML или JSON. Лично я предпочитаю JSON, поскольку он более лаконичен и по сети передается меньший объем данных нежели при передаче такого же ответа в формате XML. Разница может быть порядка нескольки сотен килобайт, но, с учетом скоростей 3G и нестабильности обмена с мобильными устройствами, эти несколько сотен килобайт могут иметь значение.

Аутентификация

Аутентификация должна производиться через https и клиент должен посылать пароль в зашифрованном виде. Процесс получения sha1 хэша NSString в Objective-C достаточно понятен и прост и приведенный код наглядно это показывает.

- (NSString *) sha1

Сервер должен сравнить полученный хэш пароля с сохраненным в его базе хэшем. В любом случае не следует ни при каких условиях передавать пароли с клиента на сервер в открытом виде. Из этого правила не существует исключений! День, когда Ваши пользователи узнают, что вы храните их пароли в открытом виде, может стать последним днем вашего стартапа. Доверие, потерянное однажды, вернуть невозможно.

RFC 2617 описывает два способа аутентификации на HTTP сервере. Первый — это Basic Access, второй Digest. Для мобильных клиентов подходит любой из этих двух методов и большинство серверных (и клиентских тоже) языков обладают встроенными механизмами для реализации таких схем аутентификации.

Если вы планируете сделать свой API публичным, вам следует также посмотреть в сторону oAuth или лучше oAuth 2.0. oAuth позволит Вашим пользователям публиковать контент, созданный в Вашем приложении, на других ресурсах без обмена ключами (логинами/паролями). oAuth также позволяет пользователям контролировать что именно находится в доступе и какие разрешения даны сторонним ресурсам.

Facebook Graph API это наиболее развитая и распространенная реализация oAuth на данный момент. Используя oAuth, пользователи Facebook могут давать доступ к своим фотографиям сторонним приложениям без публикации другой приватной и идентификационной информации (логин/пароль). Пользователь также может ограничить доступ нежелательным приложениям без необходимости менять свой пароль.

До сего момента я говорил об основах REST. Теперь переходим к сути статьи. В последующих главах я буду говорить о практических приемах, которые следует использовать при документировании, создании новых и завершении поддержки старых версий своего API…

Документирование API

Худшая документация, которую может написать разработчик сервера — это длинный, однообразный список вызовов API с описанием параметров и возвращаемых данных. Главная проблема такого подхода заключается в том, что внесение изменений в сервер и формат возвращаемых данных по мере развития проекта становится кошмаром. Я внесу кое какие предложения на этот счет, чтобы разработчик клиентского ПО понимал Вас лучше. Со временем это также поможет Вам в развитии в качестве разработчика серверного ПО.

Документация

Первым шагом я бы порекомендовал подумать об основных, высокоуровневых структурах данных (моделях), которыми оперирует ваше приложение. Затем подумайте над действиями, которые можно произвести над этими компонентами. Документация по foursquare API хороший пример, который стоит изучить перед тем как начать писать свою. У них есть набор высокоуровневых объектов, таких как места, пользователи и тому подобное. Также у них есть набор действий, которые можно произвести над этими объектами. Поскольку вы знаете высокоуровневые объекты и действия над ними в вашем продукте, создание структуры вызовов API становится проще и понятней. Например, для добавления нового места логично будет вызвать метод наподобие /venues/add

Документируйте все высокоуровневые объекты. Затем документируйте запросы и ответы на них, используя эти высокоуровневые объекты вместо простых типов данных. Вместо того, чтобы писать “Этот вызов возвращает три строковых поля, первое содержит id, второе имя, а третье описание” пишите “Этот вызов возвращает структуру (модель), описывающую место”.

Документирование параметров запроса

Давайте представим, что у Вас есть API, позволяющий пользователю входить, используя Facebok token. Вызовем этот метод как /login.

Request /login Headers Authorization: Token XXXXX User-Agent: MyGreatApp/1.0 Accept: application/json Accept-Encoding: compress, gzip Parameters Encoding type – application/x-www-form-urlencoded token – “Facebook Auth Token” (mandatory) profileInfo = “json string containing public profile information from Facebook” (optional) 

Где profileinfo высокоуровневый объект. Поскольку вы уже задокументировали внутреннюю структуру этого объекта то такого простого упоминания достаточно. Если Ваш сервер использует такие же Accept, Accept-Encoding и параметр Encoding type всегда вы можете задокументировать их отдельно, вместо повторения их во всех разделах.

Документирование параметров ответа

Ответы на вызовы API должны также быть задокументированы, основываясь на высокоуровневой модели объектов. Цитируя тот же пример foursquare, вызов метода /venue/#venueid# вернет структуру данных (модель), описывающую место проведения мероприятия.

Обмен идеями, документирование или информирование других разработчиков о том, что вы вернете в ответ на запрос станет проще если Вы задокументируете ваш API используя структуру объектов (моделей). Наиболее важный итог этой главы — это необходимость воспринимать документацию как контракт, который заключаете Вы, как разработчик серверной части и разработчики клиентских приложений (iOS/Android/Windows Phone/Чтобытонибыло).

Причины создания новых и прекращения поддержки старых версий вашего API

До появления мобильных приложений, в эпоху Web 2.0 создание разных версий API не было проблемой. И клиент (JavaScript/AJAX front-end) и сервер разворачивались одновременно. Потребители (ваши клиенты) всегда использовали самую последнюю версию клиентского ПО для доступа к системе. Поскольку вы — единственная компания, разрабатывающая как клиентскую так и серверную часть, вы полностью контролируете то как используется ваш API и изменения в нем всегда сразу же применялись в клиентской части. К сожалению это невозможно с клиентскими приложениями, написанными под разные платформы. Вы можете развернуть API версии 2, считая что все будет отлично, однако это приведет к неработоспособности приложений под iOS, использующих старую версию. Поскольку еще могут быть пользователи, использующие такие приложения несмотря на то, что вы выложили обновленную версию в App Store. Некоторые компании прибегают к использованию Push уведомлений для напоминаний о необходимости обновления. Единственное к чему это приведет — потеря такого клиента. Я видел множество айфонов, у которых было более 100 приложений, ожидающих обновления. Шансы, что ваше станет одним из них, весьма велики. Вам всегда надо быть готовым к разделению вашего API на версии и к прекращению поддержки некоторых из них как только это потребуется. Однако поддерживайте каждую версию своего API не менее трех месяцев.

Разделение на версии

Развертывание вашего серверного кода в разные папки и использование разных URL для вызовов не означает что вы удачно разделили ваш API на версии.
Так example.com/api/v1 будет использоваться версией 1.0 приложения, а ваша свежайшая и крутейшая версия 2.0 будет использовать example.com/api/v2

Когда вы делаете обновления, вы практически всегда вносите изменения во внутренние структуры данных и в модели. Это включает изменения в базе данных (добавление или удаление столбцов). Для лучшего понимания давайте представим, что ваш “новый Фейсбук” имеет вызов API, называемый /feed который возвращает объект “Лента”. На сегодня, в версии 1, ваш объект “Лента” включает URL аватарки пользователя (avatarURL), имя пользователя (personName), текст записи (feedEntryText) и время создания (timeStamp) записи. Позднее, в версии 2, вы представляете новую возможность, позволяющую рекламодателям размещать описания своих продуктов в ленте. Теперь объект “Лента” содержит, скажем так, новое поле “sourceName”, которое перекрывает собой имя пользователя при отображении ленты. Таким образом приложение должно отображать “sourceName” вместо “personName”. Поскольку приложению больше не нужно отображать “personName” если задана “sourceName”, вы решаете не отправлять “personName” если есть “sourceName”. Это все выглядит неплохо до тех пор, пока старая версия вашего приложения, версия 1 не обратится к обновленному серверу. Она будет отображать ваши рекламные записи из ленты без заголовка поскольку “personName” отсутствует. “Грамотный” способ решения такой проблемы — отправлять как “personName”, так и “sourceName”. Но, друзья, жизнь не всегда так проста. Как разработчик вы не сможете отслеживать все одиночные изменения которые когда либо были произведены с каждой моделью данных в вашем объекте. Это не очень эффективный способ внесения изменений поскольку через пол года вы практически забудете почему и как что-то было добавлено к вашему коду.

Возвращаясь к web 2.0, это не было проблемой вообще. JavaScript клиент немедленно модифицировался для поддержки изменений в API. Однако установленные iOS приложения от вас больше не зависят. Теперь их обновление — прерогатива пользователя.

У меня есть элегантное решение для хитрых ситуаций подобного толка.

Парадигма разделения на версии через URL

Первое решение — это разделение с использованием URL.
api.example.com/v1/feeds будет использоваться версией 1 iOS приложения тогда как
api.example.com/v2/feeds будет использоваться версией 2.
Несмотря на то, что звучит это все неплохо, вы не сможете продолжать создание копий вашего серверного кода для каждого изменения в формате возвращаемых данных. Я рекомендую использование такого подхода только в случае глобальных изменений в API.

Парадигма разделения на версии через модель

Выше я показал как документировать ваши структуры данных (модели). Рассматривайте эту документацию как контракт между разработчиками серверной и клиентской частей. Вам не следует вносить изменения в модели без изменения версии. Это значит, что в предыдущем случае должно быть две модели, Feed1 и Feed2.

В Feed2 есть поле sourceName и она возвращает sourceName вместо personName если sourceName установлен. Поведение Feed1 остается таким же, как это было оговорено в документации. Алгоритм работы контроллера будет примерно таким:

Вам следует переместить логику создания экземпляра класса в отдельный класс согласно паттерну Factory method. Соответствующий код контроллера должен выглядеть примерно так:

Feed myFeedObject = Feed.createFeedObject("1.0"); myFeedObject.populateWithDBObject(FeedDao* feedDaoObject); 

Где решение о версии используемого API будет принимать контроллер в соответствии с полем UserAgent текста запроса.

Дополнение:
Вместо использования номера версии из строки UserAgent, правильней будет использовать номер версии в заголовке Accept. Таким образом вместо отправки

Accept: application/json 
Accept: application/myservice.1.0+json 

Таким образом у вас появляется возможность указывать версию API для каждого запроса к REST серверу. Спасибо читателям hacker news за этот совет.

Контроллер использует метод Feed factory для создания корректного объекта feed (лента) основываясь на информации из запроса клиента (все запросы имеют в своем составе поле UserAgent которое выглядит наподобие AppName/1.0) касающейся версии. Когда вы разрабатываете сервер таким образом, любое изменение будет простым. Внесение изменений не будет нарушать имеющиеся соглашения. Просто создавайте новые структуры данных (модели), вносите изменения в factory method для создания экземпляра новой модели для новой версии и все!

При использовании такого подхода ваши приложения версий 1 и 2 могут продолжать работать с одним сервером. Ваш контроллер может создавать объекты версии 1 для старых клиентских приложений и объекты версии 2 для новых.

Прекращение поддержки

С предложенной выше парадигмой разделения API на версии через модель прекращение поддержки вашего API становится намного проще. Это очень важно на последних стадиях когда вы публикуете ваш API. Когда вы делаете глобальное обновление API проведите ревизию всех factory method в ваших моделях в соответствии с изменениями вашей бизнес логики.

Если, в ходе релиза версии 3 вашего API, вы решаете прекратить поддержку версии 1 то для этого достаточно удалить соответствующие модели и удалить строки, создающие их экземпляры в ваших factory method-ах. Создание новых версий и прекращение поддержки старых обязательно будут сопровождать ваш проект показывая насколько он гибок для поддержки ключевых решений, диктуемых бизнесом. Бизнес, неспособный к резким переменам и поворотам, обречен. Обычно неспособность к ключевым переменам обусловлена техническим несовершенством проекта. Указанная техника способна решить такую проблему.

Кэширование

Еще один немаловажный момент, касающийся производительности, которому следует уделить внимание — это кэширование. Если вы считаете, что это задача клиентского приложения подумайте хорошенько. В части 2 этой статьи я расскажу как организовать кэширование, используя средства http 1.1.

Обработка ошибок и интернационализация вашего API

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

От переводчика:
Сам я не являюсь разработчиком под iOS и web-сервисов не разрабатывал, мой уровень в этой области можно описать как «Собираюсь стать начинающим». Но тема мне интересна и статья понравилась, причем настолько, что решил перевести.

REST API, или как создать удобный веб-сервис

Для чего используются restful apis? Во-первых, для приложений и веб-сайтов, когда необходимо предоставить клиентам ресурсы. REST API основан на HTTP, поэтому его можно использовать с любым языком программирования и в любой операционной системе.

Команда is*hosting 13 апр 2023 3 мин

REST API, или как создать удобный веб-сервис

  • Что такое REST API?
    • Основополагающие принципы REST API
    • Преимущества

    Термин REST появился в 2000 году благодаря Рою Филдингу, одному из создателей протокола HTTP. Сегодня REST API используется повсеместно для обмена данными между клиентом и сервером.

    Что такое REST API?

    REST API — это сборное из двух сокращений понятие:

    Что такое API? Это Application Programming Interface; используется для работы одних программ с другими.

    Что такое REST? Это Representational State Transfer (передача состояния представления), или способ построения API архитектуры с помощью HTTP.

    REST API отвечает за взаимодействия между сервером и приложением-клиентом. Проще говоря, REST API — это технология, используемая везде, где пользователю сайта или приложения нужно предоставить данные с сервера.

    HTTP является основой REST сервисов, поскольку данный протокол реализован во всевозможных языках программирования, всех ОС и является универсальным. С помощью HTTP появляется возможность предоставить юзеру определенный ресурс по его URI.

    Ресурс — то, что необходимо показать клиенту. Например, мы создали приложение со списками и описаниями кафе в стране. Ресурсом может быть список всех городов в приложении.

    Представления ресурсов состоят из данных; метаданных, описывающих данные; и гипермедийных ссылок для перехода к следующему желаемому состоянию.

    URI — универсальный идентификатор ресурса. Например, http://allcafes.com/cities, где первая часть (http://allcafes.com) является адресом сайта или сервера, а вторая (/cities) — адресом ресурса на удаленном сервере.

    Основополагающие принципы Rest API

    принципы rest api

    Не существует определенного стандарта REST API, однако в работе, а именно в написании кода интерфейса, слеудет выделять следующие R estful API принципы:

    • Клиент-сервер (Client-Server). Это концепция, согласно которой клиент и сервер должны быть отделены друг от друга. То есть возможно вносить изменения в приложение, не влияя на структуру данных на сервере, или редактировать базу данных на сервере, не затрагивая при этом клиентское приложение.
    • Нестационарность (Stateless). Запросы могут выполняться независимо друг от друга, и каждый запрос содержит данные, необходимые для успешного предоставления данных пользователю. Сервер не должен хранить данные о проведенных операциях клиента.
    • Кэширование (Cache). Поскольку работа может быть перегружена при обработке большого количества входящих и исходящих запросов, REST API должен иметь возможность кэшировать данные (тогда пользователь сможет обращаться к этому буферу данных при повторных запросах).
    • Единый интерфейс (Uniform Interface). Наличие единого интерфейса позволяет установить более стабильную работу (например, благодаря использованию HTTP).
    • Многоуровневая система (Layered System). REST API имеют различные уровни своей архитектуры, что позволяет располагать разные серверы на разных уровнях. При таком подходе можно управлять, например, скоростью ответа более важных серверов. Подобная система позволяет существенно увеличить уровень безопасности.
    • Код по запросу (Code on Demand). Предоставление кода по требованию клиента позволяет передавать его через API для использования в приложении.

    Преимущества

    Преимущества REST API и популярность ее использования заложены в принципах и особенностях работы.

    Работая с REST API, вы получаете возможность масштабирования. Это возможно благодаря принципу разделения клиента и сервера.

    Гибкость REST API обеспечивается из-за использования HTTP и возможности работы с любыми языками программирования, в любых операционных системах. Созданная архитектура Rest API дает возможность обрабатывать любые типы запросов и форматы данных.

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

    Надежность и безопасность в REST API вытекает из принципа многоуровневой системы, поскольку она позволяет останавливать атаки на разных уровнях, предотвращая их проникновение во всю серверную архитектуру. Так, критически важные или наиболее уязвимые элементы API архитектуры могут храниться за брандмауэром, предотвращая прямое взаимодействие с ними со стороны клиента.

    Несмотря на преимущества REST, занять его позиции пытаются SOAP, GraphQL и RPC. И если SOAP, изобретенный раньше REST, и RPC, имеющий несколько версий, больше популярны в узких областях, то GraphQL считается хорошим прорывом в этой области для более сложных проектов.

    Как работает REST API?

    Как работает RESTful API?

    Принцип работы RESTful API совпадает с общим принципом работы в Интернете с любым сайтом. Пользователь (клиент) связывается с сервером с помощью API, когда ему требуется какой-либо веб-ресурс. Данный запрос обрабатывается и клиент получает запрашиваемый ресурс.

    Поэтапно работу RESTful API можно описать так:

    1. Клиент отправляет запрос на сервер. Согласно документации API, клиент форматирует (изменяет) запрос таким образом, чтобы сервер получил сведения, необходимые для обработки.
    2. Сервер аутентифицирует клиента и подтверждает, что клиент имеет право сделать этот запрос. Если аутентификация прошла неуспешно, возвращается ошибка 403.
    3. Сервер получает запрос в нужном виде, обрабатывает его.
    4. Затем сервер возвращает ответ на запрос клиенту. В ответе содержится информация об успешности выполнения команды и запрашиваемый ресурс.

    Для работы с ресурсами через REST API используются команды HTTP:

    • GET для получения информации о данных: по запросу GET /cities вы получите список всех городов, для которых собраны адреса кафе.
    • DELETE для удаления данных: по запросу DELETE /istanbul будут удалены кафе в Стамбуле, которые уже закрылись.
    • POST для добавления новых или замены старых данных: запрос POST /ankara добавит новый адрес кафе в Анкаре.
    • PUT для редактирования данных: по запросу PUT /cities можно внести правки в список городов, представленных в приложении.

    После выполнения или невыполнения любой команды, пользователь получает ответ, начинающийся с определенной цифры. Например, код, начинающийся с 2, означает успешное выполнение команды, 500 — ошибка сервера, коды с цифрой 4 в начале означают неуспешное выполнение команды и указывают на причину ошибки (401 Unauthorized — неуспешная авторизация, 404 Not found — запрашивается несуществующий ресурс и др.)

    Лучшие практики разработки REST API

    best-restapi-practicesЛучшие практики разработки REST API

    Спецификация OpenAPI, или лучшие практики разработки REST API, определяет интерфейс для описания API таким образом, который позволяет любому разработчику понять его параметры и возможности. Специалисты делятся своим опытом в открытом доступе, что и позволяет создать данную спецификацию.

    • Используйте kebab-case для URL

    Плохо: /cafeKahve или /cafe_Kahve

    • Используйте множественное число для коллекций

    Плохо: DELETE /city или DELET /City

    Хорошо: DELETE /cities

    • URL должен начинаться с коллекции и заканчиваться URI

    Плохо: GET /cafe/:cafeId/group/:groupId/price

    Хорошо: GET /cafes/:cafeId/ или GET /group/:groupId

    • Используйте глаголы в операциях, а не в URL ресурсов

    Плохо: POST /updatecafe/ или GET /getcafes

    Хорошо: PUT /cafes/

    • Используйте инструменты для разработки REST API

    Например API Blueprint и Swagger.

    • Обеспечьте безопасность

    Обязательно используйте HTTPS для всех ресурсов и сервисов.

    • Используйте коды состояния при обработке ошибок

    Рекомендуется использовать стандартные коды состояния HTTP в ответах на запросы. По кодам HTTP легче сориентироваться, произошла ли ошибка и ее суть.

    • Используйте фильтрацию, сортировку

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

    Еще больше практик работы с REST API можно найти здесь.

    Простое REST api для сайта на php хостинге

    Иногда бывает необходимо развернуть не большое рест апи для своего сайта, сделанного по технологии СПА (Vue, React или др.) без использования каких-либо фреймворков, CMS или чего-то подобного, и при этом хочется воспользоваться обычным php хостингом с минимальными усилиями на внедрение и разработку. При этом там же желательно разместить и сам сайт СПА (в нашем случае на vue).

    Использование php позволяет для построения ендпоинтов апи использовать даже статические php файлы, размещаемые просто в папках на хостинге, которые предоставляют результат при непосредственном обращении к ним. И хотя, видимо в своё время, такой подход послужил широкому распространению php мы рассмотрим далее более программистский подход к созданию апи, который очень похож на используемый в библиотеке Node.js Express и поэтому интуитивно понятен, и прост для освоения. Для это нам понадобиться библиотека «pecee/simple-router».

    Далее мы предполагаем, что у вас уже есть среда для запуска кода локально (LAMP, XAMP, docker) или как-то иначе и у вас настроено перенаправление всех запросов на индексный файл (index.php). Кроме, того мы предполагаем, что вы можете устанавливать зависимости через composer.

    Структура проекта

    На Рис.1. представлена общая структура проекта. Точкой входа является файл

    index.phpв папке web. Сама папка webявляется публично доступной папкой, и должна быть указана в настройках сервера как корневая. В папке configбудут находится настройки роутов наших ендпоинтов. В папке controllerбудут обработчики ендпоинтов маршрутов. В папке middlewaresмы разместим промежуточные обработчике роутов для выполнения авторизации перед началом основного кода ендпоинта. В папках exceptions, views и models будут соответственно исключения, html шаблон и объектные модели. Полный код проекта тут.

    Инсталляция и запуск

    Для работы необходимо инсталлировать следующее содержимое composer.json (composer install в корне проекта).

    // composer.json < "require": < "pecee/simple-router": "*", "lcobucci/jwt": "^3.4", "ext-json": "*" >, "autoload": < "psr-4": < "app\\": "" >> > 

    Обратите внимание, что ‘app\’ объявлено как префикс для namespace. Данный префикс будет использоваться при объявлении неймспейсов классов.

    Запуск всего остального кода происходит вызовом статического метода Router::route() в файле index.php

    Так же тут подключаются роуты определённые в файле config/routes.php.

    Подключение SPA на Vue.js 2 к проекту на php

    Если вы развёртываете сборку vue отдельно от апи, то этот раздел можно пропустить.

    Рассмотрим теперь то, как подключить проект на vue в данной конфигурации с использованием соответствующих маршрутов. Для этого содержимое сборки необходимо поместить в папку web. В файле маршрутов (‘/config/routes.php’) прописываем два правила:

    ; Router::setDefaultNamespace('app\controllers'); Router::get('/', 'VueController@run'); // правило 1 Router::get('/controller', 'VueController@run') ->setMatch('/\/([\w]+)/'); // правило 2 

    Для пустого (корневого) маршрута ‘/’ вызывается метод run класса VueController. Второе правило указывает что для любого явно незаданного пути будет тоже вызываться VueController, чтобы обработка маршрута происходила на стороне vue. Это правило всегда должно быть последним, чтобы оно срабатывало только тогда, когда другие уже не сработали. Метод run представляет собой просто рендеринг файла представления с помощью метода renderTemplate(), определённого в родительском классе контроллера. Здесь мы также устанавливаем префикс для классов методы которых используются в роутах с помощью setDefaultNamespace.

    renderTemplate('../views/vue/vue_page.php'); > > 

    В свою очередь представление vue_page.php тоже просто отрисовка индексного файла сборки vue.

    Итого мы подключили проект на vue к проекту на php, который уже готов к развертыванию на хостинге. Данный подход можно использовать для любых проектов на php. Осталось только рассмотреть, что собой представляет родительский класс AbstractController.

    request = Router::router()->getRequest(); $this->response = new Response($this->request); > public function renderTemplate($template) < ob_start(); include $template; return ob_get_clean(); >public function setCors() < $this->response->header('Access-Control-Allow-Origin: *'); $this->response->header('Access-Control-Request-Method: OPTIONS'); $this->response->header('Access-Control-Allow-Credentials: true'); $this->response->header('Access-Control-Max-Age: 3600'); > > 

    В конструкторе класса AbstractController определяются поля $request и $response. В $request хранится распарсенный классом Pecee\Http\Router запрос. А $response будет использоваться для создания ответов на запросы к апи. Определённый здесь метод renderTemplate используется для рендеринга представлений (html страниц). Кроме того, здесь определён метод устанавливающий заголовки для работы с политикой CORS. Его следует использовать если запросы к апи происходят не с того же адреса, т.е. если сборка vue запускается на другом веб-сервере. Теперь перейдём непосредственно к созданию апи.

    Создание REST API эндпоинтов

    Для работы с апи нам нужно произвести дополнительную обработку входящего запроса, потому что используемая библиотека не производит парсинг сырых данных. Для этого создадим промежуточный слой ProccessRawBody и добавим его как middleware в роуты для запросов к апи.

     $value) < $request->$key = $value; > > catch (\Throwable $e) < >> > > 

    Здесь мы считываем из входного потока и помещаем полученное в объект $request для дальнейшего доступа из кода в контроллерах. ProccessRawBody реализует интерфейс IMIddleware обязательный для всех middleware.

    Теперь создадим группу роутов для работы с апи использующее данный промежуточный слой.

     'api/v1', 'middleware' => [ ProccessRawBody::class ] ], function () < Router::post('/auth/sign-in', 'AuthController@signin'); Router::get('/project', 'ProjectController@index'); >);

    У этой группы определён префикс «api/v1» (т.е. полный путь запроса должен быть например ‘/api/v1/auth/sign-in’), и ранее определённое нами middleware ProccessRawBody::class, так что в контроллерах наследованных от AbstractController доступны входные переменные через $request. AuthController рассмотрим чуть позже сейчас же мы уже можем воспользоваться методами не требующими авторизации, как например ProjectController::index.

    response->json([ [ 'name' => 'project 1' ], [ 'name' => 'project 2' ] ]); > >

    Как видим, на входящий запрос, в ответе возвращаются данные о проектах.

    Остальные роуты создаются аналогичным образом.

    Авторизация по JWT токену

    Теперь перейдём к роутам требующим авторизации. Но перед этим реализуем вход и получение jwt-токена. Для создания токена и его валидации мы будем использовать библиотеку “ lcobucci/jwt” Всё это будет у нас выполнятся по роуту определённому ранее ‘/auth/sign-in’. Соответственно в AuthController::singin у нас прописана логика выдачи jwt-токена после авторизации пользователя.

    builder() // Configures the issuer (iss claim) ->issuedBy('http://example.com') // Configures the audience (aud claim) ->permittedFor('http://example.org') // Configures the id (jti claim) ->identifiedBy('4f1g23a12aa') // Configures the time that the token was issue (iat claim) ->issuedAt($now) // Configures the expiration time of the token (exp claim) ->expiresAt($now->modify('+2 minutes')) // Configures a new claim, called "uid" ->withClaim('uid', $user->id) // Configures a new header, called "foo" ->withHeader('foo', 'bar') // Builds a new token ->getToken($config->signer(), $config->signingKey()); return $this->response->json([ 'accessToken' => $token->toString() ]); > >

    Здесь используется симметричная подпись для jwt с использованием секретного ключа ‘секретный_ключ’. По нему будет проверятся валидность токена при запросах к апи. Ещё можно использовать асимметричную подпись с использованием пары ключей.

    Можно также отметить, что можно создавать сколько угодно клаймов ->withClaim(‘uid’, $user->id) и сохранять там данные которые можно будет потом извлекать из ключа. Например, id пользователя для дальнейшей идентификации запросов от этого пользователя. Токен выдан на 2 минуты (->expiresAt($now->modify(‘+2 minutes’))) после чего он становится не валидным. ->issuedBy и ->permittedFor используются для oath2.

    Теперь создадим группу роутов защищённую авторизацией. Для этого определим для группы роутов промежуточный слой Authenticate::class.

     'api/v1', 'middleware' => [ ProccessRawBody::class ] ], function () < Router::post('/auth/sign-in', 'AuthController@signin'); Router::get('/project', 'ProjectController@index'); Router::group([ 'middleware' =>[ Authenticate::class ] ], function () < // authenticated routes Router::post('/project/create', 'ProjectController@create'); Router::post('/project/update/', 'ProjectController@update') ->where(['id' => '[\d]+']); >); >); 

    Как видите, группа с авторизацией объявлена внутри группы с префиксом “api/v1 ”. Рассмотрим роут ‘/project/update/’. Здесь объявлен параметр id который определён как число. В метод update, контроллера Projectcontroller будет передана переменная $id содержащая значение этого параметра. Ниже приведён пример запроса и ответ.

     > */ public function update(int $id): string < // код обновляющий проект return $this->response->json([ [ 'response' => 'OK', 'request' => $this->request->project, 'id' => $id ] ]); > > 

    Вернёмся теперь к промежуточному слою Authenticate::class с помощью которого происходит авторизация запросов к апи.

    parser()->parse($tokenString); if ( !$config->validator()->validate( $token, new SignedWith( new Sha256(), InMemory::plainText('секретный_ключ') ), new ValidAt(new FrozenClock(new DateTimeImmutable())) ) ) < throw new NotAuthorizedHttpException('Токен доступа не валиден или просрочен'); >$userId = $token->claims()->get('uid'); $request['uid'] = $userId; > > 

    Здесь, считывается заголовок ‘Authorization: Bearer [token]’ (так называемая bearer авторизация) и извлекается оттуда токен, которые клиенты получают после логина и должны посылать со всеми запросами, требующими авторизацию. Далее с помощью парсера jwt-токен-строчка парсится. И дальше с помощью валидатора распарсенный токен валидируется. Метод validate() возвращает true or false. В случае не валидного токена выбрасывается исключение NotAuthorizedException. Если токен валидный, то мы извлекаем из него id пользователя $token->claims()->get(‘uid’) и сохраняем в переменную запроса $request, чтобы его можно было использовать дальше в контроллере. NotAuthorizedException определяется следующим образом:

    В завершении рассмотрим ещё обработку ошибок. В файле routes.php запишем следующие строчки:

    httpCode(401); break; > case Exception::class: < $response->httpCode(500); break; > > if (PROD) < return $response->json([]); > else < return $response->json([ 'status' => 'error', 'message' => $exception->getMessage() ]); > >);

    В итоге файл routes.php будет выглядеть следующим образом:

    Рис. 2. Итоговая структура проекта

    ; use app\middlewares\< Authenticate, ProccessRawBody >; use Pecee\< Http\Request, SimpleRouter\SimpleRouter as Router >; const PROD = false; Router::setDefaultNamespace('app\controllers'); Router::get('/', 'VueController@run'); Router::group([ 'prefix' => 'api/v1', 'middleware' => [ ProccessRawBody::class ] ], function () < Router::post('/auth/sign-in', 'AuthController@signin'); Router::get('/project', 'ProjectController@index'); Router::group([ 'middleware' =>[ Authenticate::class ] ], function () < // authenticated routes Router::post('/project/create', 'ProjectController@create'); Router::post('/project/update/', 'ProjectController@update') ->where(['id' => '[\d]+']); >); >); Router::get('/controller', 'VueController@run') ->setMatch('/\/([\w]+)/'); Router::error(function(Request $request, Exception $exception) < $response = Router::response(); switch (get_class($exception)) < case NotAuthorizedHttpException::class: < $response->httpCode(401); break; > case Exception::class: < $response->httpCode(500); break; > > if (PROD) < return $response->json([]); > else < return $response->json([ 'status' => 'error', 'message' => $exception->getMessage() ]); > >); 

    Заключение

    В итоге у нас получилось небольшое, простое REST api для небольших проектов которое можно использовать на обычном php хостинге с минимальными трудозатратами на его (хостинга) настройку. Полный код проекта тут.

    Больше настроек роутов можно найти здесь. Вместо рассмотренной библиотеки «pecee/simple-router» можно использовать любую другую аналогичную библиотеку или даже микрофреймворк Slim.

    Пс. Если вы используете публичный репозиторий или придерживаетесь бестпрактис, то не следует хранит секретный ключ в коде. Для этого можно использовать переменные среды или локальные файлы, которые не добавляются в репозиторий. Код работы с jwt токенами можно выделить в отдельный класс в папке services.

    How to host your API/backend on a virtual private server (VPS)

    You wrote a REST or GraphQL API (with a database) and now you’re wondering where you can host it? You want to keep the costs low for a hobby project? Your primary domain is a different tech field and while you heard your backend colleagues chatting about things, you never set one up yourself? Don’t know where to begin when it comes to deploying an API on a virtual private server (VPS)?

    If you answered “yes” to any or all of these questions, you’re in the same position I was in and this post can help! In this post I will explain:

    • How to convert your application with possible database to a Docker Compose setup.
    • How to get your application running on your VPS.
    • How to configure a domain for your server.
    • How to setup SSL so you can make secured requests to your server.

    Because all of this is far from my day-to-day business as a mobile developer, and I was unable to locate any comprehensive documentation, spending days googling and figuring everything out, I wrote this guide in hopes of helping other developers, as well as serving as a reference for me. Send me a ping if you notice errors or anti-patterns!

    Quick links

    • How to actually connect to your VPS
    • Dockerize your application
      — Add a Dockerfile for your server
      — Docker Compose to use your server with a database
      — Provide environment variables to your server
      — Start, stop and monitor your application
    • Connecting your domain to your VPS
    • Activating SSL for your server
    • Add port forwarding to your API

    Motivation and background information

    So, I have this mobile app that requires a backend. Until recently, I used Firebase Realtime Database in conjunction with a Firebase Cloud Function feeding that database with data. Over time, my database grew so large that viewing and editing the data inside became extremely challenging. I decided it’s time to migrate to a real database and a real backend. Getting the chance to learn backend development was a good opportunity for me anyway. I began reading into Ktor and was able to quickly write something that worked well on my computer.

    Where we start

    Let’s agree on a common starting point before digging into the details of setting up the VPS. I’m starting with a Ktor (JVM) server application, that stores and reads data into/from a PostgreSQL database. As of now, my application runs on localhost and connects to a database running locally. Also, I have already implemented my server so it expects six secrets as environment variables (database access, API keys, etc.).

    While I built a JVM backend, it doesn’t really matter for the rest of this post if you built a Python, Go or whatever application.

    Now, let’s get this thing into the cloud!

    Why a VPS and not AWS, Azure or anything?

    I don’t know if it’s the same for you, but every time I try to get started with AWS, Azure or GCP I am totally overwhelmed by all the services they provide and I absolutely don’t understand their pricing models. While I’m fine with reading documentation, I want to know what I have to pay upfront..and for a hobby project that won’t earn me anything and has a couple of requests per day I don’t want to spend 100€/month or even more.

    So I ordered a private server for 2,60€/month, I know what I pay and with my application in a docker container I can always take it and deploy it somewhere else when needed.

    How to actually connect to your VPS

    You got an IP address and a root password for your server, but what’s next? That might sound super stupid, but it took me some minutes until I realized “ah stupid me, there’s ssh��‍♂️”

    So open up a terminal and type ssh root@123.123.123.123 — obviously using your server’s IP here. It will ask for your password and voilà, you’re connected. After this you will want to install some basics like curl, Git and Docker.

    Dockerize your application

    I hadn’t worked with Docker for some years since it’s not my daily business. However, I still knew some basics and what needs to be done more or less. Still, it took me a lot of time until I found the correct commands and configuration for everything, so this section might be boring for you if you’re a Docker pro or very helpful if you just get started with it.

    Dockerfile for your server

    The very first thing you will need is a Dockerfile in your root directory which builds and runs your server. This file’s content depends on what type of server you have, but here is my configuration for a Ktor JVM server built with Gradle:

    FROM openjdk:11
    EXPOSE 8080:8080
    RUN mkdir /app
    COPY . /app
    WORKDIR /app
    RUN ./gradlew installDist --stacktrace
    WORKDIR /app/build/install/yourServer/bin
    CMD ["./yourServer"]

    As a base image I’m using a simple image that comes with OpenJDK 11. The EXPOSE instruction documents that I would like to map port 8080, on which my application is listening on, from the container to port 8080 of the hosting machine. The /app directory that is being created is just a helper folder to safe some typing. With RUN ./gradlew installDist I’m building the binary (JAR) for my app which will be generated in /build/install/yourServer/bin . Replace yourServer with your application’s name. The CMD instruction then tells Docker what command to execute once the container gets started. CMD can only exist once per Dockerfile . Here, we’re just running the JAR that we built earlier, so effectively your server gets started.

    That’s for you main application, now let’s add the PostgreSQL database.

    Docker Compose to use your server with a database

    With Docker Compose you can define and run multiple Docker containers. Since we want to run or server with a database, we need to create a docker-compose.yml file in the root directory next. Then we add a very basic config to it that would run our application and create a PostgreSQL database:

    The version at the top defines the Docker Compose version for this snippet. Next we define the services that we would to run whenever we run this Docker Compose file.

    One service is our server, named api_service . With build . we tell Docker that this service is build from a local Dockerfile — the one we defined before. With ports we define which ports we would like to expose from this service to the hosting machine. We put the same value here we put in the Dockerfile previously, where we documented that we want to map port 8080 to port 8080. With restart: always we tell Docker that we want this service, as the statement suggests, to always be restarted in case of a failure, outage etc. And the last property depends_on: db says that this service depends on another service, named db . Docker will wait to start this service until the service db got started, so we launch our database before our server.

    For our database service db we do a similar setup. As a base image we’re not using a local Dockerfile , but the latest official PostgreSQL image. Additionally, we define volumes in the service. It means that we mount the path db-data on the hosting machine to the path /var/lib/postgresql/data of the container. Without it, each time we stop or restart our db service, we would start with an empty database. This way the data stored in the database is persisted between shutdowns! The volume also needs to be defined on top level of the Docker Compose file.

    With the command docker-compose up we could actually run our server and database now, but your server wouldn’t know what’s the address of your database and what credentials to use. Let’s do this next.

    Provide environment variables to your server

    When thinking about how to provide environment variables, e.g. my PostgreSQL user and password or API keys, to my application, one major question came to my mind:

    How do I actually provide secrets without hardcoding them in cleartext in my docker-compose.yml or elsewhere on my server?

    So, I absolutely didn’t want to just put them in my Docker files, which are pushed to GitHub, but simply storing them in text files on the server also felt…wrong.

    Then I learned about Docker secrets! Docker can store any sensitive information encrypted on the system and only provide the values to containers to which you granted access to.

    What I, however, understood only much later: once you decide to use Docker secrets, your defined Docker containers will run in swarm mode, which not only changes the way you start & stop your containers, but also some instructions in your docker-compose.yml need replacements, since they’re not supported in swarm mode!

    But first, let us create a secret!

    echo "postgresPassword" | docker secret create POSTGRES_PASSWORD -

    It’s important to note that, after creating it, there is no way to read the value again! If you need it elsewhere, make sure to also save it somewhere else. To delete the secret you can type:

    docker secret rm POSTGRES_PASSWORD

    and with this command you can list all secrets that you created:

    docker secret ls

    Create all the secrets you need and modify your docker-compose.yml .

    We need to define in a top level secrets block which secrets we’re going to use. The external keyword says that this secret comes from an external resource, in our case from Docker secrets.

    Each service that needs to read that secret also needs a secrets block, defining the secrets the service needs to access. In the environment block we’re then defining that we’d like to have our secrets as environment variables for the container. It is very important to note that the environment variable will not contain the secret, but the path to the file that contains the secret. Adjust your code accordingly to read the secret from that file.

    Furthermore we cannot use our local Dockerfile anymore, but need to provide an image. To build an image from your Dockerfile type:

    docker build -t yourServerImage .

    and then reference yourServerImage:latest in your docker-compose.yml .

    Start, stop and monitor your application

    If you didn’t need the secrets from above and your application doesn’t run in swarm mode, you can simply type docker-compose up now and your application is running.

    If you, however, used secrets as I did, that command won’t work. Instead we need to deploy our swarm! We do that via:

    docker stack deploy -c docker-compose.yml yourServer

    The last argument is a custom name for your stack. To stop your stack type:

    docker stack rm yourServer

    To check your running services type:

    docker service ls
    docker ps -a

    If you want to view the logs of any service defined in your Docker Compose file you can type:

    docker service logs $yourServiceName

    To restart a service running in your stack first find the id of the service and then force it to update, which causes a restart:

    docker service ls
    docker service update --force $id

    And that’s it about Docker! Your server should now be working and be reachable via your server’s IP http://123.123.123.123:8080 .

    Connecting your domain to your VPS

    This step is optional and only applicable if you have a domain that you want to use. What we want to achieve is that your domain, let’s say myAwesomeDomain.com , redirects to your server’s IP address 123.123.123.123 . We can achieve this by configuring the DNS settings for your domain, which is usually available via the settings menu of your domain.

    Once you found the DNS settings, create a new DNS entry (or modify an existing one) of Type A. As RDATA , Destination or IP address put the IP address of your VPS. The Name or Host field is up to you. You can modify the (probably) existing one saying myAwesomeDomain.com or create a new prefix, e.g. api , so your entry says api.myAwesomeDomain.com . In latter case only that subdomain would point to your VPS.

    But there is a second step needed. Your VPS also needs to be aware that its domain name is myAwesomeDomain.com . You might not be aware, but on your VPS is (probably) a server already running, one that listens on port 80 (the default HTTP port) and returns some static default page whenever you visit http://123.123.123.123 ! In my case that was an Apache2 server and I can only explain the configuration for that one.

    Go to the directory /etc/apache2/sites-available/ . In this directory you will find configuration files for so called virtual hosts. In a virtual host we define a config per website/domain (in theory you could host multiple websites on your VPS with different domains). Let’s create a new config, e.g. with touch myAwesomeDomain.conf , and open it with a text editor of your choice.

    Paste this content inside the file:

     
    ServerAdmin admin@ myAwesomeDomain .com
    ServerName myAwesomeDomain .com
    ServerAlias www. myAwesomeDomain .com
    ErrorLog "/var/logs/ myAwesomeDomain .com/error_log"
    CustomLog "/var/logs/ myAwesomeDomain .com/access_log"

    With this config we’re telling Apache we have a virtual host with domain myAwesomeDomain.com , listening on port 80, along with some basic config where Apache should log data and which email address to use in case of errors.

    Now we need to activate this configuration. We do this via the command:

    sudo a2ensite myAwesomeDomain.conf

    and then restart Apache webserver:

    sudo service apache2 restart

    Congrats! Your VPS is now reachable via myAwesomeDomain.com ! If not, allow up to two days for all the DNS name servers to update the record you just added/modified, so they’re aware the domain myAwesomeDomain.com should point to your VPS’ IP address (it took roughly five minutes for me, though).

    But, there is a problem: your API is running on port 8080 and currently your Apache2 server serves content on port 80. We will fix this next!

    Activating SSL for your server

    Before we continue I’ll highly recommend activating SSL on your webserver, now, to avoid some possible conflicts with the Apache2 configuration. If you don’t want SSL on your webserver you can skip this section.

    To activate SSL, so your data is transported securely via HTTPS over the internet, you need an SSL certificate from an official certificate authority. You can buy one. or get one for free from Let’s Encrypt. To be honest, I don’t know what’s the difference between buying one and getting a free one, but we go with the latter approach.

    Luckily, th step is pretty much automated through Certbot, an open-source tool to generate certificates with Let’s Encrypt. The installation process differs based on the OS your VPS is using, so hop over to the instruction section of Certbot and get it installed.

    Afterwards, start the certification via:

    sudo certbot --apache

    or pass any other argument for the webserver your VPS is using.

    After you’re done, there should be a new virtual host configuration in the previously mentioned folder /etc/apache2/sites-available/ , configuring a virtual host for port 443, the HTTPS port. With this, your webserver should now be reachable via https://myAwesomeDomain.com .

    Add port forwarding to your API

    Our API is listening on port 8080, but our webserver only accepts connections on port 80 or 443, respectively. What we need is a rule that internally forwards requests to port 8080, so your API is hit.

    Hop into your virtual host configuration (the one created by Certbot or the one you created, if you didn’t enable SSL) and add the following lines:

    RewriteEngine on
    SSLProxyEngine on # only needed when used with SSL
    RewriteRule ^.*$ http://%:8080%

    Here we’re enabling an Apache2 extension RewriteEngine and provide a rule with a regular expression, that should match all incoming requests and redirect them to port 8080, keeping the path component the request was called with.

    Save the file. Then we need to enable the extension a2enmod we’re now using and restart the webserver with:

    sudo a2enmod rewrite
    sudo systemctl restart apache2

    And with that, any calls that work via http://123.123.123.123:8080/getStuff should now also work when calling https://myAwesomeDomain.com/getStuff !

    I hope you found this post useful and would be grateful if you have any suggestions or improvements.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *