nginx
Sinatra
unicorn

nginx + unicorn + sinatra でAPIを作ってみました

More than 1 year has passed since last update.

はじめに

初めまして。@rueyjyeです。
外国人ですので、日本語は多少おかしいと思いますが、よろしくお願いいたします。

今回は練習でnginx、unicorn、sinatraを使って、APIサーバーを作ってみました。
APIはOpen API(Swagger)という、API定義の仕様を満たされるAPIを作ってみました。
Swagger Editorを使って、APIを定義した後sinatra用のコードを自動生成して実装しました。

nginx + unicorn + sinatraでサーバー起動する

・sinatraの準備

config.ru
require "rubygems"
require "sinatra"

require File.expand_path '../my_app.rb', __FILE__

run MyApp
my_app.rb
require "rubygems"
require "sinatra/base"

class MyApp < Sinatra::Base

  get '/' do
    'Hello, nginx and unicorn!'
  end

end

・unicornの準備

unicorn用のフォルダを作る

mkdir tmp
mkdir tmp/sockets
mkdir tmp/pids
mkdir log

今のディレクトリ

/usr/local/app
├── config.ru
├── my_app.rb
├── log
├── tmp
│   ├── pids
│   └── sockets
└── unicorn.rb

unicorn.rbを作成

unicorn.rb
# set path to app that will be used to configure unicorn,
# note the trailing slash in this example
@dir = "/usr/local/app/"

worker_processes 2
working_directory @dir

timeout 30

# Specify path to socket unicorn listens to,
# we will use this in our nginx.conf later
listen "#{@dir}tmp/sockets/unicorn.sock", :backlog => 64

# Set process id path
pid "#{@dir}tmp/pids/unicorn.pid"

# Set log file paths
stderr_path "#{@dir}log/unicorn.stderr.log"
stdout_path "#{@dir}log/unicorn.stdout.log"

unicornの起動

unicorn -c /usr/local/app/unicorn.rb -E development -D

-cはunicornの設定ファイル、-EはRackの環境変数、-Dはデーモン起動

unicornの停止

cat /usr/local/app/tmp/pids/unicorn.pid | xargs kill -QUIT

・nginxの準備

nginx.configの設定

/etc/nginx/nginx.conf
# this sets the user nginx will run as,
#and the number of worker processes
user nobody nogroup;
worker_processes  1;

# setup where nginx will log errors to
# and where the nginx process id resides
error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
  worker_connections  1024;
  # set to on if you have more than 1 worker_processes
  accept_mutex off;
}

http {
  include       /etc/nginx/mime.types;

  default_type application/octet-stream;
  access_log /tmp/nginx.access.log combined;

  # use the kernel sendfile
  sendfile        on;
  # prepend http headers before sendfile()
  tcp_nopush     on;

  keepalive_timeout  5;
  tcp_nodelay        on;

  gzip  on;
  gzip_vary on;
  gzip_min_length 500;

  gzip_disable "MSIE [1-6]\.(?!.*SV1)";
  gzip_types text/plain text/xml text/css
     text/comma-separated-values
     text/javascript application/x-javascript
     application/atom+xml image/x-icon;

  # use the socket we configured in our unicorn.rb
  upstream unicorn_server {
    server unix:/usr/local/app/tmp/sockets/unicorn.sock
        fail_timeout=0;
  }

  # configure the virtual host
  server {
    # replace with your domain name
    server_name my-sinatra-app.com;
    # replace this with your static Sinatra app files, root + public
    root /usr/local/app/public;
    # port to listen for requests on
    listen 80;
    # maximum accepted body size of client request
    client_max_body_size 4G;
    # the server will close connections after this time
    keepalive_timeout 5;

    location / {
      try_files $uri @app;
    }

    location @app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      # pass to the upstream unicorn server mentioned above
      proxy_pass http://unicorn_server;
    }
  }
}

nginxの起動

service nginx start

nginxの停止

service nginx stop

・サーバー起動

1.unicorn起動

unicorn -c /usr/local/app/unicorn.rb -E development -D

2.nginx起動

service nginx start

3.サーバーにhttp request、 get / の時、 ”Hello, nginx and unicorn!” 返せば成功

APIの実装

Open APIについて詳細の説明は下記のドキュメントと
SwaggerでRESTful APIの管理を楽にするこの記事を参照してもいいと思います。
Open APIのドキュメントhttps://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md

今回はgithubにあるサンプルを使って実装すると思います。 https://github.com/OAI/OpenAPI-Specification/tree/master/examples/v2.0/yaml

・APIを定義する

petstore-expanded.yaml
swagger: "2.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  description: A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
  termsOfService: http://swagger.io/terms/
  contact:
    name: Swagger API Team
    email: foo@example.com
    url: http://madskristensen.net
  license:
    name: MIT
    url: http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
host: petstore.swagger.io
basePath: /api
schemes:
  - http
consumes:
  - application/json
produces:
  - application/json
paths:
  /pets:
    get:
      description: |
        Returns all pets from the system that the user has access to
      operationId: findPets
      parameters:
        - name: tags
          in: query
          description: tags to filter by
          required: false
          type: array
          collectionFormat: csv
          items:
            type: string
        - name: limit
          in: query
          description: maximum number of results to return
          required: false
          type: integer
          format: int32
      responses:
        "200":
          description: pet response
          schema:
            type: array
            items:
              $ref: '#/definitions/Pet'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
    post:
      description: Creates a new pet in the store.  Duplicates are allowed
      operationId: addPet
      parameters:
        - name: pet
          in: body
          description: Pet to add to the store
          required: true
          schema:
            $ref: '#/definitions/NewPet'
      responses:
        "200":
          description: pet response
          schema:
            $ref: '#/definitions/Pet'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
  /pets/{id}:
    get:
      description: Returns a user based on a single ID, if the user does not have access to the pet
      operationId: find pet by id
      parameters:
        - name: id
          in: path
          description: ID of pet to fetch
          required: true
          type: integer
          format: int64
      responses:
        "200":
          description: pet response
          schema:
            $ref: '#/definitions/Pet'
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
    delete:
      description: deletes a single pet based on the ID supplied
      operationId: deletePet
      parameters:
        - name: id
          in: path
          description: ID of pet to delete
          required: true
          type: integer
          format: int64
      responses:
        "204":
          description: pet deleted
        default:
          description: unexpected error
          schema:
            $ref: '#/definitions/Error'
definitions:
  Pet:
    allOf:
      - $ref: '#/definitions/NewPet'
      - required:
        - id
        properties:
          id:
            type: integer
            format: int64

  NewPet:
    required:
      - name  
    properties:
      name:
        type: string
      tag:
        type: string    

  Error:
    required:
      - code
      - message
    properties:
      code:
        type: integer
        format: int32
      message:
        type: string

・sinatraのコードを生成する

Swagger Editorで、上部のGenerate Serverをクリックし、sinatraをクリック。
p1.png

・サーバーのディレクトリに置く

/usr/local/app
├── config.ru
├── my_app.rb
├── swagger.yaml
├── Gemfile
├── api
│   └── default_api.rb
├── lib
│   └── swaggering.rb
├── log
├── tmp
│   ├── pids
│   └── sockets
└── unicorn.rb

・サーバー再起動

1.unicorn停止

cat /usr/local/app/tmp/pids/unicorn.pid | xargs kill -QUIT

2.unicorn起動

unicorn -c /usr/local/app/unicorn.rb -E development -D

※ nginxは既に起動すれば、再起動は不要です

・curlで試す

# GET pets/{id}で試す
curl 設定したhost/api/pets/001

{"message":"yes, it worked"}
# 返せば成功です

・APIファイルにやりたい処理を書く

default_api.rb
... #上は略

MyApp.add_route('GET', '/api/pets/{id}', {
  "resourcePath" => "/Default",
  "summary" => "",
  "nickname" => "find_pet_by_id", 
  "responseClass" => "inline_response_200", 
  "endpoint" => "/pets/{id}", 
  "notes" => "Returns a user based on a single ID, if the user does not have access to the pet",
  "parameters" => [
    {
      "name" => "id",
      "description" => "ID of pet to fetch",
      "dataType" => "int",
      "paramType" => "path",
    },
    ]}) do
  cross_origin
  # the guts live here
  # ここに処理を書く
  if params[:id] == "001" then
    status 400
    {"message" => "something wrong"}.to_json
  end
end

... #下は略

# GET pets/{id}で試す
curl 設定したhost/api/pets/001

HTTP/1.1 400 Bad Request
# status 400返せば成功です

勉強したこと

・ OpenAPI Specの書き方
・ sinatra, unicornの実装

参考

Nginx Proxied to Unicorn
SwaggerでRESTful APIの管理を楽にする