The finished product is here. This is the API that I actually manage my diary. : joy: lol http://www.kojimaiton-philosophy.com/api/diaries/
Anyone can get GET
, but POST``PUT``DELETE
cannot be accessed by anyone, so add an access token to the header.
Replace XXXXXX_XXXXXX
with the acquired access token.
GET
Get a diary in the list. Pagination, filters and limit offsets are also available.
$ curl http://localhost/api/diaries/
Get one date diary with the date as the primary key.
$ curl http://www.kojimaiton-philosophy.com/api/diaries/2016-12-19/
POST
Put the necessary contents in the diary and create a new one * Since the date is the primary key, you cannot POST the same date twice.
$ curl -X POST http://www.kojimaiton-philosophy.com/api/diaries/ -d "date=2017-03-25" -d "title=Diary title" -d "body=Contents of the diary" -d "publishing=true" -H "Authorization: JWT XXXXXX_XXXXXX"`
PUT
Specify the date you want to update in the path, rewrite it with the data you want to change, and update.
$ curl -X PUT http://www.kojimaiton-philosophy.com/api/diaries/2017-03-25/ -d "date=2017-03-25" -d "title=Change the title of the diary" -d "body=Diary change" -d "publishing=false" -H "Authorization: JWT XXXXXX_XXXXXX"
DELETE
Specify the date you want to delete in the path and delete the data.
$ curl -X DELETE http://www.kojimaiton-philosophy.com/api/diaries/2017-03-25/ -H "Authorization: JWT XXXXXX_XXXXXX"
CentOS: 7.3.1 Python: 3.6.0 Django: 1.10.6 Restframework: 3.6.2 MySQL: 5.7.17 Nginx: 1.11.13
update yum
$ sudo yum update -y
install git
$ sudo yum install git -y
Put the necessary libraries
$ sudo yum install zlib-devel bzip2 bzip2-devel readline-devel openssl-devel sqlite3 sqlite-devel gcc -y
Turn off firewalld for practice
$ systemctl stop firewalld
firewalld automatic start / stop
$ systemctl disable firewalld
SELinux confirmation
$ getenforce
If Enforcing, edit below and SELINUX=SELINUX enforcing=Rewrite to disabled.
$ sudo vi /etc/selinux/config
If rewritten, restart the machine to reflect the settings
$ sudo shutdown -r now
Make sure it is Disabled
$ getenforce
$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ exec $SHELL -l
Verification
$ pyenv -v
Check version
$ pyenv install -l
This time 3.6.0 Installation * It takes a long time to complete the installation.
$ pyenv install 3.6.0
Verification
$ pyenv versions
Change default
$ pyenv global 3.6.0
Verification
$ pyenv versions
Django installation(python3.Pip is available by default from 6)
$ pip install django
REST_Frame_Work installation
$ pip install djangorestframework
filtering
$ pip install django-filter
Access token by JWT
$ pip install djangorestframework-jwt
Salt mysql with Django
$ pip install PyMySQL
Run Django on a production server
$ pip install uwsgi
Create a project
$ django-admin startproject django_rest_framework
$ cd django_rest_framework
Corrected the host name. Enter the IP / domain you are using.
$ sudo vi django_rest_framework/settings.py
ALLOWED_HOSTS = ['160.16.65.138', 'localhost', '127.0.0.1',]
manage.Start the server with the directory containing the py file and check the connection with a browser. http://Domain name:It can be confirmed at 8000. Ctrl to shut down the server+C
$ python manage.py runserver 0.0.0.0:8000
Settings while the server is running.The debug mode of py is set to true by default, so if you edit the file, it will be reflected on the server immediately.
Check connection from browser
It's easy to use SQLite, but here we will use MySQL, which can be used firmly and universally. (Although the setting is long: joy :)
The db that was created by executing it earlier.Delete sqlite3.
$ ls
db.sqlite3 diary manage.py
$ rm db.sqlite3
MariaDB is included by default from centos7, so delete it
$ sudo yum remove mariadb-libs -y
If you were using another version of mysql, delete it because that data may remain
$ rm -rf /var/lib/mysql/
Add MySQL repository
$ sudo yum -y install http://dev.mysql.com/get/mysql57-community-release-el6-7.noarch.rpm
Check what can be installed
$ yum repolist enabled | grep "mysql.*-community.*"
Check the information with the info command
$ yum info mysql-community-server
Installation
$ sudo yum -y install mysql-community-server
Version confirmation
$ mysql --version
Automatic start
$ sudo chkconfig mysqld on
Start-up
$ sudo systemctl start mysqld
Check the default password
$ sudo vi /var/log/mysqld.log
Find the following temporary password in the log file. This time, my password is(E,Looks like irsThV0uB
2017-04-01T20:05:05.561190Z 1 [Note] A temporary password is generated for root@localhost: N4IYvEp&)6%!
When you do the following, you will be asked for a password, so enter the password you found above
$ mysql -u root -p
If you do not change the temporary password, it will not accept any processing, so change it. However, please note that this password will not be accepted unless it contains at least 8 characters, letters, numbers, and symbols.
$ ALTER USER root@localhost IDENTIFIED BY '@Mysql0001';
Once set, exit mysql once
$ exit;
Add a line to the following file and set Japanese.[mysqld]Under the character-set-server=Added utf8.
$ sudo vi /etc/my.cnf
The description looks like this ↓
[mysqld]
character-set-server=utf8
Reflect the settings
$ sudo systemctl restart mysqld
Create a DB for use with Django
$ mysql -u root -p
Here, the name is diarydb.
$ create database diarydb;
Finish after creating
$ exit
Edit settings.py
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
import os
#Import pymysql
import pymysql
#Changed to use mysql
pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
#Database name
'NAME': 'diarydb',
#username
'USER': 'root',
#password
'PASSWORD': '@Mysql0001',
#The IP address and host of the server. Blank is localhost
'HOST': '',
#port
'PORT': '3306',
'OPTIONS': {
#Strictly check constraints
'sql_mode': 'traditional',
},
#Test user
'TEST_NAME': 'auto_tests',
}
}
Check if Django and MySQL can connect
Create a migration file
$ python manage.py makemigrations
Reflect in DB based on migration file
$ python manage.py migrate
DB setup finished ... Long: joy:
Create diary app
$ cd django_rest_framework
$ python manage.py startapp diary
Click here for the current directory structure. It's like adding files little by little here: muscle:
└── django_rest_framework
├── diary
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── django_rest_framework
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ ├── settings.cpython-36.pyc
│ │ ├── urls.cpython-36.pyc
│ │ └── wsgi.cpython-36.pyc
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
The model is one-to-one with the DB.
Create models.py
$ vi diary/models.py
diary/models.py
# coding: utf-8
from django.db import models
from datetime import date
class Diary(models.Model):
date = models.DateField(default=date.today, primary_key=True)
title = models.CharField(max_length=128)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
publishing = models.BooleanField(default=True)
Define the created model
in settings.py
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#Add diary
'diary',
]
Migrate DB again
$ python manage.py makemigrations
$ python manage.py migrate
Enter Django's DB management screen
Create user for admin
$ python manage.py createsuperuser
Start the server.
$ python manage.py runserver 0.0.0.0:8000
Access the administration screen http: // your IP / domain: 8000 / admin /
Enter the ʻUsername and
Passwordcreated in
createsuperuser` earlier to enter the management screen.
You can click on the GUI to edit or add models.
Add the model created earlier here so that it can be operated from the GUI.
$ vi diary/admin.py
diary/admin.py
# coding: utf-8
from django.contrib import admin
from .models import Diary
@admin.register(Diary)
class Diary(admin.ModelAdmin):
pass
DIARY
has been added to the admin screen: muscle:
Let's make some diaries here for use in the API.
Diarys -> Add diary -> SAVE
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#Add diary
'diary',
#Add framework
'rest_framework',
]
Limit the fields to be output to API in Serializer class
$ vi diary/serializer.py
diary/serializer.py
# coding: utf-8
from rest_framework import serializers
from .models import Diary
class DiarySerializer(serializers.ModelSerializer):
class Meta:
model = Diary
fields = ('date', 'title', 'body', 'publishing',)
ViewSet is a controller
$ vi diary/views.py
diary/views.py
# coding: utf-8
from rest_framework import viewsets
from .models import Diary
from .serializer import DiarySerializer
class DiaryViewSet(viewsets.ModelViewSet):
queryset = Diary.objects.all()
serializer_class = DiarySerializer
Urls is a URL router setting
Register the created DiaryViewSet
$ vi diary/urls.py
diary/urls.py
# coding: utf-8
from rest_framework import routers
from .views import DiaryViewSet
router = routers.DefaultRouter()
router.register(r'diaries', DiaryViewSet)
Routing settings accessed from the outside
$ vi django_rest_framework/urls.py
django_rest_framework/urls.py
# coding: utf-8
from django.conf.urls import url, include
from django.contrib import admin
from diary.urls import router as diary_router
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(diary_router.urls)),
]
Start the server
$ python manage.py runserver 0.0.0.0:8000
Connect with browser
http: // my IP / domain: 8000 / api / diaries /
When I hit it with curl, json
is returned.
$ curl http://160.16.65.138:8000/api/diaries/
[{"date":"2017-04-15","title":"Title test","body":"Contents test","publishing":true},{"date":"2017-04-16","title":"title","body":"contents!","publishing":true}]
It is important to note here that the characters are garbled when viewed with json from the pull-down menu with the button next to GET
in the above picture. : scream:
It may not be garbled depending on the version of Chrome, but it is automatically converted to ʻUTF-8. If you look at it without specifying ʻUTF-8
in Safari etc., it seems that the characters are garbled.
To fix the following, you have to specify ʻUTF-8 here for the
json` returned to the client.
The REST Framework
documentation says UTF-8 by default: joy:
$ vi diary/renderers.py
Creating a ʻUTF8CharsetJSONRenderer class with ʻUTF-8
The default rest_framework.renderers.JSONRenderer
seems to be ʻUTF-8`, but I couldn't. .. ..
diary/renderers.py
from rest_framework.renderers import JSONRenderer
class UTF8CharsetJSONRenderer(JSONRenderer):
charset = 'utf-8'
Add the following to settings.py
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'diary.renderers.UTF8CharsetJSONRenderer',
)
}
Garbled characters fixed: muscle:
Change the IP address to your own and request: point_up:
POST(Create)
Create New
$ curl -X POST http://160.16.65.138:8000/api/diaries/ -d "date=2017-04-22" -d "title=Diary title" -d "body=POST test" -d "publishing=true"
PUT(Update)
update
$ curl -X PUT http://160.16.65.138:8000/api/diaries/2017-04-22/ -d "date=2017-04-22" -d "title=Change the title of the diary" -d "body=PUT test" -d "publishing=false"
GET(Read)
Get list
$ curl http://160.16.65.138:8000/api/diaries/
Get by date
$ curl http://160.16.65.138:8000/api/diaries/2017-04-22/
DELETE(Delete)
Delete
$ curl -X DELETE http://160.16.65.138:8000/api/diaries/2017-04-22/
Add pagination
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
REST_FRAMEWORK = {
#add to
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 3,
'DEFAULT_RENDERER_CLASSES': (
'diary.renderers.UTF8CharsetJSONRenderer',
)
}
When you get it, next
and previous
are added to the root of json, and you can follow the previous and next pages.
$ curl http://160.16.65.138:8000/api/diaries/
{
count: 4,
next: "http://160.16.65.138:8000/api/diaries/?limit=3&offset=3",
previous: null,
results: [
{
date: "2017-04-15",
title: "Title test",
body: "Contents test",
publishing: true
},
{
date: "2017-04-16",
title: "title",
body: "contents!",
publishing: true
},
{
date: "2017-04-21",
title: "hoge",
body: "ssss",
publishing: true
}
]
}
Edit views.py
$ vi diary/views.py
diary/views.py
# coding: utf-8
from rest_framework import viewsets
from .models import Diary
from .serializer import DiarySerializer
class DiaryViewSet(viewsets.ModelViewSet):
queryset = Diary.objects.all()
serializer_class = DiarySerializer
#Add filter
filter_fields = ('publishing',)
Edit settings.py
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
REST_FRAMEWORK = {
#Add filter
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
#Add pagination
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 3,
'DEFAULT_RENDERER_CLASSES': (
'diary.renderers.UTF8CharsetJSONRenderer',
)
}
Try filtering only those with false
? Publish = false
in the request
$ curl http://160.16.65.138:8000/api/diaries/?publing=false
{
count: 1,
next: null,
previous: null,
results: [
{
date: "2017-04-22",
title: "hogehoge",
body: "hoge",
publishing: false
}
]
}
Edit settings.py
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
REST_FRAMEWORK = {
#Add JWT certification
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
'NON_FIELD_ERRORS_KEY': 'detail',
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
#Add filter
'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
#Add pagination
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 3,
'DEFAULT_RENDERER_CLASSES': (
'diary.renderers.UTF8CharsetJSONRenderer',
)
}
#Add JWT certification
JWT_AUTH = {
#Try invalidating the token expiration here
'JWT_VERIFY_EXPIRATION': False,
}
Edit ʻurls.py
$ vi django_rest_framework/urls.py`
django_rest_framework/urls.py
# coding: utf-8
from django.conf.urls import url, include
from django.contrib import admin
from diary.urls import router as diary_router
#Add authentication
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include(diary_router.urls)),
#Add authentication
url(r'^api-auth/', obtain_jwt_token),
]
Request with ʻadmin user ʻusername
and password
created by createsuperuser
$ curl http://160.16.65.138:8000/api-auth/ -d "username=XXXXXXX&password=XXXXXXXX"
If successful, token
will be included in the response.
{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJlbWFpbCI6Im11dS5rb2ppbWEudGVzdEBnbWFpbC5jb20iLCJ1c2VybmFtZSI6ImRqYW5nbyIsImV4cCI6MTQ5Mjg1NTMxMH0.m07BcTiAkA79HZ0BC8BsgYOA-SbqmC5GMN5g_QBizZw"}
If the user name and password are different, it will fail as follows
{"detail":["Unable to login with provided credentials."]}
Edit views.py
and authenticate
$ vi diary/views.py
diary/views.py
# coding: utf-8
from rest_framework import viewsets
from .models import Diary
from .serializer import DiarySerializer
#Add authentication
from rest_framework import permissions
class DiaryViewSet(viewsets.ModelViewSet):
queryset = Diary.objects.all()
serializer_class = DiarySerializer
#Add filter
filter_fields = ('publishing',)
#Add authentication If you want to authenticate all CURD(permissions.IsAuthenticated,)To
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Create New
$ curl -X POST http://160.16.65.138:8000/api/diaries/ -d" date = 2017-04-21 "-d" title = authentication "-d" body = authentication test "-d" publishing = true "
`
Since there is no access token, it is OK if it fails as follows!
{"detail":"Authentication credentials were not provided."}
POST with the acquired access token * Change the access token to your own
$ curl -X POST http://160.16.65.138:8000/api/diaries/ -d "date=2017-04-10" -d "title=Authentication" -d "body=Authenticationのテスト" -d "publishing=true" -H "Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJlbWFpbCI6Im11dS5rb2ppbWEudGVzdEBnbWFpbC5jb20iLCJ1c2VybmFtZSI6ImRqYW5nbyIsImV4cCI6MTQ5Mjg1NTMxMH0.m07BcTiAkA79HZ0BC8BsgYOA-SbqmC5GMN5g_QBizZw"
Once created, make sure everything except GET
is authenticated: fist:
permissions.IsAuthenticatedOrReadOnly
, so if you take ʻOrReadOnly,
GET` will also be authenticated.When you access the current route, Django defaults to: http://160.16.65.138:8000/
Edit settings.py
and set DEBUG = False
according to the wording at the bottom. * If you set DEBUG = False
, the file changes cannot be reflected automatically while starting the server. So be careful
You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.
$ vi django_rest_framework/settings.py
django_rest_framework/settings.py
...abridgement
# DEBUG =If set to False, it will be a normal 404 page.
DEBUG = False
...abridgement
When I accessed it, it became a normal 404 page!
tests.Remove py
$ rm diary/tests.py
Create a test directory. Put the test here
$ mkdir diary/tests
To be recognized as a python directory`__init__.py`Add. Nothing in particular is described.
$ vi diary/tests/__init__.py
Create test_diary.py
for authentication and CRUD testing
$ vi diary/tests/test_diary.py
diary/tests/test_diary.py
# coding: utf-8
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework_jwt.compat import get_user_model
from rest_framework_jwt.settings import api_settings
class DiaryTest(APITestCase):
def setUp(self):
#Issuance of access token
User = get_user_model()
self.username = 'test_user'
self.email = '[email protected]'
self.user = User.objects.create_user(self.username, self.email)
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(self.user)
token = jwt_encode_handler(payload)
self.auth = 'JWT {0}'.format(token)
self.url = '/api/diaries/'
def test_diary_api(self):
# POST
data = {
"date": "2011-11-11",
"title": "title",
"body": "body",
"publishing": True,
}
response = self.client.post(self.url, data, HTTP_AUTHORIZATION=self.auth)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, data)
# GET
expected_get_data = {
"count": 1,
"next": None,
"previous": None,
"results": [
{
"date": "2011-11-11",
"title": "title",
"body": "body",
"publishing": True,
}
]
}
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, expected_get_data)
# PUT
data2 = {
"date": "2010-10-10",
"title": "title",
"body": "body",
"publishing": False,
}
response = self.client.put(self.url + '2011-11-11/', data2, HTTP_AUTHORIZATION=self.auth)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, data2)
# DELETE
response = self.client.delete(self.url + '2010-10-10/', HTTP_AUTHORIZATION=self.auth)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
The following will run test_xxxx.py in tests
.
$ ./manage.py test
The test passed!
[xxxxx@tk2-208-13884 django_rest_framework]$ ./manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.049s
OK
Destroying test database for alias 'default'...
Create a file of nginx.repo
because we will add a repository
$ sudo vi /etc/yum.repos.d/nginx.repo
nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=1
install nginx
$ sudo yum install nginx -y
Check version
$ nginx -v
Auto start setting
$ sudo systemctl enable nginx
Start-up
$ sudo systemctl start nginx
Check the display. http: // IP of my server /
Below is my example.
http://160.16.65.138/
above is
/usr/share/nginx/html/index.html`Create a file with the name XXX.conf under /etc/nginx/conf.d
Here, it is called server.conf, and the request received at number 80 is forwarded to number 8000.
$ sudo vi /etc/nginx/conf.d/server.conf
server.conf
server {
listen 80;
#An accessible IP address or domain. Below is my example
server_name 160.16.65.138;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_max_temp_file_size 0;
location / {
#Forwarding destination
proxy_pass http://localhost:8000;
}:joy:
}
Reboot nginx to reflect the settings
$ sudo systemctl restart nginx
When executed, it will be accessible from the browser with the default HTTP 80
, so 8000
is unnecessary.
python manage.py runserver 0.0.0.0:8000
Try to keep it running in the background even if you exit` from the server.
Set password for root
$ sudo passwd root
Become the root
$ su
Run Django with a production daemon
$ uwsgi --http :8000 --module django_rest_framework.wsgi --daemonize /var/log/uwsgi-django.log
Now it should be running in the background!
To stop ʻuwsgirunning in the background,
kill the
PID` of the running server.
Find out
$ netstat -ntlp
It is displayed next to ʻuwsgi, but since it is
PID, In this case
kill -9 32215` will stop the server
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 32181/nginx: master
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 861/sshd
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 32215/uwsgi
tcp 0 0 127.0.0.1:43047 0.0.0.0:* LISTEN 32215/uwsgi
tcp6 0 0 :::22 :::* LISTEN 861/sshd
tcp6 0 0 :::3306 :::* LISTEN 19382/mysqld
How to read ʻuwsgi` seems to be whiskey? I read "Uesugi": joy:
https://github.com/MuuKojima/django_rest_framework
http://www.kojimaiton-philosophy.com/api/diaries/ While expanding, we will continue to operate for a lifetime: muscle: This is operated on AWS!
http://qiita.com/redamoon/items/eabaacabb5b1a0c34ca3 http://qiita.com/seizans/items/05a909960820dd92a80a http://racchai.hatenablog.com/entry/2016/05/08/070000 http://var.blog.jp/archives/70125676.html
Recommended Posts