J'oublie souvent comment utiliser Ruby, j'ai donc créé une application web simple en Ruby pour la rééducation. Aussi, j'écrirai comment le faire au cas où je l'oublierais.
C'est une putain d'application de chat appelée Nopochat. C'est un chat qui fait date que vous pouvez envoyer du contenu difficile à parler tout en maintenant la sécurité psychologique en ajoutant une jolie fin de "-mo" au message.
Le contenu présenté dans cet article est destiné uniquement à la pratique d'écriture de programmes et n'est pas destiné à être utilisé dans un environnement de production.
Utilisez Docker. Aucune installation de Ruby requise.
Tout d'abord, coupez un répertoire approprié et démarrez le développement.
$ mkdir sinatra-chat && cd $_
Si vous souhaitez gérer votre projet avec Git, c'est une bonne idée de récupérer et de définir GitHub gitignore.
$ git init
$ curl https://raw.githubusercontent.com/github/gitignore/master/Ruby.gitignore -o .gitignore
Obtenez la version de l'image Ruby que vous souhaitez utiliser sur https://hub.docker.com/_/ruby. Cette fois, j'utiliserai «2.7-slim».
Commencez par initialiser Gemfile
.
$ docker run --rm --volume $(pwd):/app --workdir /app ruby:2.7-slim bundle init
$ docker run --rm --volume $(pwd):/app --workdir /app ruby:2.7-slim bundle add sinatra
Après avoir confirmé que Gemfile
et Gemfile.lock
ont été générés, écrivez Dockerfile
.
Dockerfile
FROM ruby:2.7-slim
WORKDIR /app
COPY Gemfile ./
COPY Gemfile.lock ./
RUN bundle config --local set path 'vendor/bundle'
RUN bundle install
CMD bundle exec ruby index.rb
docker-compose.yml
version: '3'
services:
app:
build: .
volumes:
- .:/app
- /app/vendor/bundle
ports:
- 127.0.0.1:4567:4567
Créez ʻindex.rb`, qui est le corps principal de l'application.
index.rb
require 'sinatra'
configure do
set :bind, '0.0.0.0'
end
get '/' do
'Hello Sinatra!'
end
Allez sur http: // localhost: 4567 / et si vous voyez "Hello Sinatra!", Vous avez réussi.
Dans l'état actuel des choses, même si vous modifiez le fichier, il ne sera reflété que si vous redémarrez le serveur Sinatra. Il est utile d'activer le rechargement lors du rechargement avant de commencer le développement.
$ docker-compose run --rm app bundle add sinatra-contrib
index.rb
require 'sinatra'
+require 'sinatra/reloader' if settings.development?
Puisque le contenu du chat sera conservé plus tard, nous le stockerons dans une variable de classe appelée @@ chats
pour le moment.
index.rb
get '/' do
@@chats ||= []
erb :index, locals: {
chats: @@chats.map{ |chat| add_suffix(chat) }.reverse
}
end
post '/' do
@@chats ||= []
@@chats.push({ content: params['content'], time: Time.now } )
redirect back
end
def add_suffix(chat)
{ **chat, content: "#{chat[:content]}Aussi" }
end
Utilisez erb pour le modèle HTML. Si vous coupez le répertoire views /
et y stockez ʻindex.erb, vous pouvez l'appeler avec ʻerb: index
.
views/index.erb
<form action="/" method="post">
<input name="content" placeholder="Publier" />
<button type="submit">Envoyer</button>
</form>
<table>
<% chats.each do |chat| %>
<tr>
<td><%= chat[:content] %></td>
<td><%= chat[:time] %></td>
</tr>
<% end %>
</table>
Au minimum, vous devriez pouvoir discuter.
Enregistrez le contenu du chat sur MySQL. Installez mysql2 Gem.
$ docker-compose run --rm app bundle add mysql2
Obtenez votre version préférée de MySQL sur https://hub.docker.com/_/mysql et utilisez-la. Définissez également les informations de connexion en tant que variable d'environnement pour l'application.
docker-compose.yml
version: '3'
services:
app:
build: .
volumes:
- .:/app
- /app/vendor/bundle
ports:
- 127.0.0.1:4567:4567
+ environment:
+ - MYSQL_HOST=db
+ - MYSQL_USER=root
+ - MYSQL_PASS=secret
+ - MYSQL_DATABASE=nopochat_development
+ db:
+ image: mysql:5.7
+ volumes:
+ - .:/app
+ - /var/lib/mysql
+ environment:
+ - MYSQL_ROOT_PASSWORD=secret
index.rb
require 'sinatra'
require 'sinatra/reloader' if settings.development?
+require 'mysql2'
(Ce qui suit est omis)
Installez les packages requis pour utiliser mysql2 Gem.
Dockerfile
FROM ruby:2.7-slim
WORKDIR /app
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ libmariadb-dev \
+ && apt-get clean \
+ && rm -rf /var/lib/apt/lists/*
(Ce qui suit est omis)
Définissez une méthode pour obtenir le client de base de données en fonction des variables d'environnement définies.
index.rb
def db_client()
Mysql2::Client.default_query_options.merge!(:symbolize_keys => true)
Mysql2::Client.new(
:host => ENV['MYSQL_HOST'],
:username => ENV['MYSQL_USER'],
:password => ENV['MYSQL_PASS'],
:database => ENV['MYSQL_DATABASE']
)
end
Cette fois, la spécification est d'initialiser la base de données lors de l'accès à GET / initialize
(bien qu'une telle spécification ne soit pas possible en fonctionnement réel ...)
index.rb
get '/initialize' do
client = Mysql2::Client.new(
:host => ENV['MYSQL_HOST'],
:username => ENV['MYSQL_USER'],
:password => ENV['MYSQL_PASS']
)
client.query("DROP DATABASE IF EXISTS #{ENV['MYSQL_DATABASE']}")
client.query("CREATE DATABASE IF NOT EXISTS #{ENV['MYSQL_DATABASE']} CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci")
client = db_client
client.query(<<-EOS
CREATE TABLE IF NOT EXISTS chats (
id INT AUTO_INCREMENT,
name TEXT,
content TEXT,
time DATETIME,
PRIMARY KEY(id)
)
EOS
)
redirect '/'
end
Définissez une méthode pour entrer et sortir des données d'une table appelée chats.
index.rb
def chat_push(content, name="Anonyme")
db_client.prepare(
"INSERT into chats (name, content, time) VALUES (?, ?, NOW())"
).execute(name, content)
end
def chats_fetch()
db_client.query("SELECT * FROM chats ORDER BY time DESC")
end
Réécrivez GET /
et POST /
en utilisant la méthode définie.
index.rb
get '/' do
- @@chats ||= []
+ chats = chats_fetch
erb :index, locals: {
- chats: @@chats.map{ |chat| add_suffix(chat) }.reverse
+ chats: chats.map{ |chat| add_suffix(chat) }
}
end
post '/' do
- @@chats ||= []
- @@chats.push({ content: params['content'], time: Time.now } )
+ chat_push(params['content'])
redirect back
end
Lancez l'application et accédez à http: // localhost: 4567 / initialize pour la lancer. Désormais, même si vous redémarrez l'application, le contenu avec lequel vous avez discuté ne disparaîtra pas.
DB (MySQL) est utilisé pour le stockage de session.
Définissez une table ʻusers avec un nom d'utilisateur et un mot de passe et une table
sessions` pour stocker les sessions. À l'origine, le mot de passe doit être chiffré afin qu'il ait un hachage. Vous devez également supprimer périodiquement des sessions.
index.rb
client.query(<<-EOS
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT,
name VARCHAR(255) UNIQUE,
password TEXT,
PRIMARY KEY(id),
INDEX key_index (name)
);
EOS
)
client.query(<<-EOS
CREATE TABLE IF NOT EXISTS sessions (
id INT AUTO_INCREMENT,
session_id VARCHAR(255) UNIQUE,
value_json JSON,
PRIMARY KEY(id),
INDEX key_index (session_id)
);
EOS
)
user_push('admin', 'admin')
Définit le traitement d'ajout / d'authentification d'utilisateur.
index.rb
def user_push(name, pass)
db_client.prepare(
"INSERT into users (name, password) VALUES (?, ?)"
).execute(name, pass)
end
def user_fetch(name, pass)
result = db_client.prepare("SELECT * FROM users WHERE name = ?").execute(name).first
return unless result
result[:password] == pass ? result : nil
end
Définit le processus d'ajout / d'acquisition de session.
index.rb
def session_save(session_id, obj)
db_client.prepare(
"INSERT into sessions (session_id, value_json) VALUES (?, ?)"
).execute(session_id, JSON.dump(obj))
end
def session_fetch(session_id)
return if session_id == ""
result = db_client.prepare("SELECT * FROM sessions WHERE session_id = ?").execute(session_id).first
return unless result
JSON.parse(result&.[](:value_json))
end
Ajoutez `require'sinatra / cookies '' pour utiliser les cookies.
index.rb
require 'sinatra'
require 'sinatra/reloader' if settings.development?
+require 'sinatra/cookies'
require 'mysql2'
Définissez «POST / login» et «GET / logout».
index.rb
post '/login' do
if user = user_fetch(params['name'], params['pass'])
cookies[:session_id] = SecureRandom.uuid if cookies[:session_id].nil? || cookies[:session_id] == ""
session_save(cookies[:session_id], { name: user[:name] })
end
redirect back
end
get '/logout' do
cookies[:session_id] = nil
redirect back
end
Modifiez «GET /» et «POST /».
index.rb
get '/' do
+ name = session_fetch(cookies[:session_id])&.[]("name")
chats = chats_fetch
erb :index, locals: {
+ name: name,
chats: chats.map{ |chat| add_suffix(chat) }
}
end
post '/' do
- chat_push(params['content'])
+ name = session_fetch(cookies[:session_id])&.[]("name")
+ chat_push(params['content'], name)
redirect back
end
Réécrivez la partie formulaire de View afin que le formulaire de connexion s'affiche lorsque vous n'êtes pas connecté et que le formulaire de publication s'affiche après la connexion.
vieqs/index.erb
<% if name %>
<p>Bonjour<%= name %>M.</p>
<a href="/logout">Se déconnecter</a>
<form action="/" method="post">
<input name="content" placeholder="Publier" />
<button type="submit">Envoyer</button>
</form>
<% else %>
<form action="login" method="post">
<input name="name" placeholder="Nom d'utilisateur">
<input name="pass" placeholder="mot de passe">
<button type="submit">S'identifier</button>
</form>
<% end %>
Si vous pouvez accéder à http: // localhost: 4567 / initialize et vous connecter en tant qu'utilisateur ʻadmin`, vous avez réussi.
Modifiez docker-compose.yml
comme suit.
Créez deux applications et ajoutez un nouveau conteneur Nginx qui sera le serveur Web.
Fermez le port 4567 dans Sinatra et ouvrez le port 8080 pour Nignx.
Nginx récupère votre version préférée sur https://hub.docker.com/_/nginx et l'utilise.
docker-compose.yml
version: '3'
services:
- app:
+ app1: &app
build: .
volumes:
- .:/app
- /app/vendor/bundle
- ports:
- - 127.0.0.1:4567:4567
environment:
- MYSQL_HOST=db
- MYSQL_USER=root
- MYSQL_PASS=secret
- MYSQL_DATABASE=nopochat_development
+ app2:
+ <<: *app
+ web:
+ image: nginx:1.19-alpine
+ volumes:
+ - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
+ ports:
+ - 127.0.0.1:8080:80
(Ce qui suit est omis)
Placez le fichier de configuration Nginx. Essayez de trier ʻapp1 et ʻapp2
.
nginx/default.conf
upstream apps {
server app1:4567;
server app2:4567;
}
server {
listen 80;
proxy_set_header Host $host:8080;
location / {
proxy_pass http://apps;
}
}
Accédez à http: // localhost: 8080 /. Si le processus de connexion échoue, la session changera et vous serez déconnecté à chaque fois que vous y accédez.
Le processus d'ajout de "mo" à la fin du mot mentionné au début
def add_suffix(chat)
{ **chat, content: "#{chat[:content]}Aussi" }
end
Écrivons ceci dans Rust et appelons-le depuis Ruby.
Obtenez votre version préférée sur https://hub.docker.com/_/rust. Cette fois, nous utiliserons "rust: 1.46-slim".
Créez un projet Rust dans un répertoire appelé rust_lib
avec la commande suivante.
$ docker run \
--rm \
--volume $(pwd):/app \
--workdir /app \
--env USER=root \
rust:1.46-slim cargo new rust_lib --lib
$ cd rust_lib
Si vous le souhaitez, prenez-le sur GitHub .gitignore et définissez-le.
$ curl https://raw.githubusercontent.com/github/gitignore/master/Rust.gitignore -o .gitignore
Ajoutez libc crate dans Cargo et spécifiez le type de crate à «" dylib "».
rust_lib/Cargo.toml
[dependencies]
libc = "0.2.77"
[lib]
name = "rust_lib"
crate-type = ["dylib"]
J'écrirai le processus dans Rust.
rust_lib/src/lib.rs
extern crate libc;
use libc::*;
use std::ffi::{CStr, CString};
#[no_mangle]
pub extern fn add_suffix(s: *const c_char) -> CString {
let not_c_s = unsafe { CStr::from_ptr(s) }.to_str().unwrap();
let not_c_message = format!("{}Aussi", not_c_s);
CString::new(not_c_message).unwrap()
}
Construire.
$ docker run \
--rm \
--volume $(pwd):/app \
--workdir /app \
rust:1.46-slim cargo build
Il réussit si rust_lib / target / release / librust_lib.so
est construit.
$ nm target/release/librust_lib.so | grep add_suffix
00000000000502c0 T add_suffix
Ruby FFI J'écrirai un processus pour appeler Rust depuis Ruby en utilisant Gem.
$ docker-compose run --rm app1 bundle add ffi
index.rb
require 'sinatra'
require 'sinatra/reloader' if settings.development?
require 'sinatra/cookies'
require 'mysql2'
+require 'ffi'
ʻExtend FFI :: Library pour lire et utiliser
rust_lib / target / release / librust_lib.so`. Spécifiez l'argument et le type de retour en vous référant à FFI wiki.
index.rb
#Configuration du module pour appeler depuis Rust
module RustLib
extend FFI::Library
ffi_lib('rust_lib/target/release/librust_lib.so')
attach_function(:add_suffix, [:string], :string)
end
Modifiez «GET /».
index.rb
get '/' do
name = session_fetch(cookies[:session_id])&.[]("name")
chats = chats_fetch
erb :index, locals: {
name: name,
- chats: chats.map{ |chat| add_suffix(chat) }
+ chats: chats.map{ |chat| { **chat, content: RustLib::add_suffix(chat[:content]).force_encoding("UTF-8") } }
}
end
c'est tout. Le produit fini est le suivant.
https://github.com/s2terminal/sinatra-chat
Recommended Posts