tag:crieit.net,2005:https://crieit.net/tags/Sinatra/feed 「Sinatra」の記事 - Crieit Crieitでタグ「Sinatra」に投稿された最近の記事 2022-09-20T14:35:24+09:00 https://crieit.net/tags/Sinatra/feed tag:crieit.net,2005:PublicArticle/18292 2022-09-06T02:40:41+09:00 2022-09-20T14:35:24+09:00 https://crieit.net/posts/BBS-programming たよりない BBS programming <p><a target="_blank" rel="nofollow noopener" href="https://crieit-net.translate.goog/posts/BBS-programming?_x_tr_sl=ja&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp">transrate to</a></p> <h2 id="掲示板 : Bulletin board system をプログラムする。"><a href="#%E6%8E%B2%E7%A4%BA%E6%9D%BF+%3A+Bulletin+board+system+%E3%82%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%99%E3%82%8B%E3%80%82">掲示板 : Bulletin board system をプログラムする。</a></h2> <p><strong>Bulletin board system</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Bulletin_board_system">https://en.wikipedia.org/wiki/Bulletin_board_system</a></p> <p><strong>youtube 資料</strong><br /> 古いの<br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/Dddbe9OuJLU">https://youtu.be/Dddbe9OuJLU</a></p> <p>わりと最近 ( 温故知新 )<br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/wqqhSz2fLVw">https://youtu.be/wqqhSz2fLVw</a></p> <p>いろんな言語で書いてみたいが、web framework を使って プログラムする方法を知るために、まず、Ruby と web framework を sinatra にして、どうやって掲示板をプログラミングするかを並べていく。</p> <p><strong>sinatra</strong><br /> <a target="_blank" rel="nofollow noopener" href="https://sinatrarb.com/">https://sinatrarb.com/</a></p> <p>Ruby と Sinatra での Hello World までは、</p> <p><a href="https://crieit.net/posts/Ruby-Sinatra-web">できるだけわかりやすい Ruby + Sinatra で web アプリ</a></p> <p>Hello World までのことが理解できたら、手順はわかるので、BBS って何だっけ?ということを浅く、ふかーく掘ってみると拡張するアイデアがでる基礎がならされてくる。<br /> まず、なんだっけ?というとこから、これを見てコピペで実行した。</p> <blockquote> <p><strong>100行未満かつGo標準ライブラリだけで作る掲示板</strong><br /> 著者:クジラ飛行机<br /> <a target="_blank" rel="nofollow noopener" href="https://news.mynavi.jp/techplus/article/gogogo-9/">https://news.mynavi.jp/techplus/article/gogogo-9/</a></p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://www.tohoho-web.com/ex/golang.html">Go 言語でのコンパイルまでの手順</a>を知っていれば、全くくじけず掲示板プログラムのミニマムな仕様が感じられるいい記事だと思った。</p> <p>その後、二週間か三週間くらい、<a target="_blank" rel="nofollow noopener" href="https://www.reddit.com/r/programming/comments/23umjd/4chan_source_code_leak/">4chan</a> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/8chan">8chan</a> の事件、なぜ <a target="_blank" rel="nofollow noopener" href="https://www.google.com/search?q=京都市バス事件 あめぞう&newwindow=1&sxsrf=ALiCzsYiYV3nSBQM_kQ3X7lkeKCmTWrcYA:1662551080471&ei=KIQYY_m7HNDZhwOh8rD4Dg&ved=0ahUKEwj578m0zYL6AhXQ7GEKHSE5DO8Q4dUDCA4&uact=5&oq=京都市バス事件 あめぞう&gs_lcp=Cgdnd3Mtd2l6EAMyBQgAEIAEOgoIABBHENYEELADOgcIABCABBAESgQIQRgASgQIRhgAUMoHWKcTYJwcaAFwAXgAgAFfiAH4BJIBATeYAQCgAQHIAQrAAQE&sclient=gws-wiz">2 チャンネルが開始</a>されたのか、<a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/西鉄バスジャック事件">関連事件</a>など記事や映像を掘って読み続けた。</p> <p>The case for anonymity online<br /> <a target="_blank" rel="nofollow noopener" href="https://www.ted.com/talks/christopher_moot_poole_the_case_for_anonymity_online?language=en">https://www.ted.com/talks/christopher_moot_poole_the_case_for_anonymity_online?language=en</a></p> <blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Fredrick_Brennan">Fredrick Brennan</a> @[email protected]</p> <p>https://twitter.com/fr_brennan/status/1197461945307131905?s=20&t=od7Bucc3KHDWPnixxI1Bcg</p> <p>https://mobile.twitter.com/fr_brennan/status/1566445269771755523<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/infinitechan/status/848139757758562304?s=20&t=XDUEGtuxavkH4t-m8kYI6A">https://twitter.com/infinitechan/status/848139757758562304?s=20&t=XDUEGtuxavkH4t-m8kYI6A</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/hcfrm">Leaked voting machine BIOS passwords may implicate Q-friendly county clerk | Ars Technica</a></p> <p>Who Owns 4chan?<br /> <a target="_blank" rel="nofollow noopener" href="https://www.wired.com/story/who-owns-4chan/">https://www.wired.com/story/who-owns-4chan/</a></p> </blockquote> <p>掲示板を、使えるレベルにするには、どうやってコメントを掃除するかという点が重要なことだ、きっと。<br /> 古典的な chat のルールや機能をよく確認するべき。minecraft や、<a target="_blank" rel="nofollow noopener" href="https://wiki.minetest.net/Privileges/ja">minetest の privileges</a> などが参考にがなるかもしれない。<br /> 管理者ではなく、ユーザー側でブロックしたい書き込みを非表示にする <a target="_blank" rel="nofollow noopener" href="https://help.twitter.com/ja/using-twitter/blocking-and-unblocking-accounts">twitter のようなブロック機能</a>は、オールドスクールな BBS にはなかったが今後あったほうがいい。</p> <blockquote> <p>華原朋美さん「ヤフコメさん達には傷つけられてきた」法的処置明かす 投稿者はどうやって特定する?<br /> <a target="_blank" rel="nofollow noopener" href="https://archive.ph/z1o2c">https://archive.ph/z1o2c</a><br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/s0o3rlwsjOk">https://youtu.be/s0o3rlwsjOk</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://news.yahoo.co.jp/newshack/inside/nonfiction_award_2021.html">今日この賞が発表されて、明日からYahoo!ニュースのコメント欄は荒れるでしょう。</a><br /> <a target="_blank" rel="nofollow noopener" href="https://youtu.be/F-TI3UONAHY">https://youtu.be/F-TI3UONAHY</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/judo_gonoi/status/1568055692023705602?s=20&t=Fvazn5DcjEiwBx89yng2eA">「もうYahooのコメントも怖いです。」</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://twitter.com/narita_yusuke/status/1567714001294921728?s=20&t=cO0-6ole4g71HLPZBtParw">「来世ではヤフコメとTwitterで不眠不休で人に文句つけつづける生活を送ってみたい」</a></p> </blockquote> <p>hashtag #dropkiwifarms<br /> <a target="_blank" rel="nofollow noopener" href="https://en.m.wikipedia.org/wiki/Kiwi_Farms">https://en.m.wikipedia.org/wiki/Kiwi_Farms</a><br /> 速報: CloudflareによるKiwi Farmsのブロック https://web.gnusocial.jp/post/2022/09/20/</p> <p>reddit:Aaron Swartz<br /> <a target="_blank" rel="nofollow noopener" href="https://www.troddit.com/r/aaronswartz">https://www.troddit.com/r/aaronswartz</a></p> <p>さらに、日本の掲示板文化(主に perl で作られる BBS 1995 - 2006 )について<br /> 『あやしいわーるどの歴史』(2006/09/28 現在)<br /> <a target="_blank" rel="nofollow noopener" href="http://f16.aaacafe.ne.jp/~stwalker/">http://f16.aaacafe.ne.jp/~stwalker/</a></p> <hr /> <p>広告表示なし</p> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/m953n">https://rentry.co/m953n</a></p> <hr /> <h1 id="たよりない BBS programming"><a href="#%E3%81%9F%E3%82%88%E3%82%8A%E3%81%AA%E3%81%84+BBS+programming">たよりない BBS programming</a></h1> <p>とりあえず、<a target="_blank" rel="nofollow noopener" href="https://matz.rubyist.net/20050420.html#p02">ローカルホストで戰います</a>。</p> <p>アドレス <code>http://127.0.0.1</code> のポートナンバー 4567<br /> <code>localhost:4567</code></p> <p>詳細は、<br /> できるだけわかりやすい Ruby + Sinatra で web アプリ<br /> <a href="https://crieit.net/posts/Ruby-Sinatra-web">https://crieit.net/posts/Ruby-Sinatra-web</a><br /> をクリアするとわかります。</p> <p><a href="https://crieit.now.sh/upload_images/2862c8dd49e72077e4c96240d41a05bd631d807e6d151.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/2862c8dd49e72077e4c96240d41a05bd631d807e6d151.png?mw=700" alt="image" /></a></p> <p>色は違いますが、掲示板の感じはこうなります。</p> <h2 id="Files &amp; Directories"><a href="#Files+%26amp%3B+Directories">Files & Directories</a></h2> <pre><code>BBS ├── bbs.rb ├── bbsdata.db ├── lockfile ├── views │ ├── badrequest.erb │ ├── board.erb │ ├── ca.erb │ ├── created.erb │ ├── layout.erb │ ├── login.erb │ ├── loginfail.erb │ └── logout.erb │ ├── public │ └── css │ └── style.css │ │ #[ bundle init ] ├── Gemfile │ #[ bundle install ] ├── nender #auto generated └── Gemfile.lock #auto generated </code></pre> <hr /> <h2 id="BBS directories"><a href="#BBS+directories">BBS directories</a></h2> <h3 id="BBS/Gemfile"><a href="#BBS%2FGemfile">BBS/Gemfile</a></h3> <pre><code class="bundler">#frozen_string_literal: true source "https://rubygems.org" gem "activerecord" gem "sqlite3" gem "sinatra" gem "webrick" </code></pre> <hr /> <h2 id="All in one piece"><a href="#All+in+one+piece">All in one piece</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/n7v9x">https://rentry.co/n7v9x</a></p> <h2 id="BBS/views Directory"><a href="#BBS%2Fviews+Directory">BBS/views Directory</a></h2> <p>どれも数行だけのコード。</p> <h3 id="BBS/views/layout.erb"><a href="#BBS%2Fviews%2Flayout.erb">BBS/views/layout.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/ngg3s">https://rentry.co/ngg3s</a></p> <h3 id="BBS/views/login.erb"><a href="#BBS%2Fviews%2Flogin.erb">BBS/views/login.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/gyd6v">https://rentry.co/gyd6v</a></p> <h3 id="BBS/views/logout.erb"><a href="#BBS%2Fviews%2Flogout.erb">BBS/views/logout.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/t92tk">https://rentry.co/t92tk</a></p> <h3 id="BBS/views/loginfail.erb"><a href="#BBS%2Fviews%2Floginfail.erb">BBS/views/loginfail.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/2fnu9">https://rentry.co/2fnu9</a></p> <h3 id="BBS/views/badrequest.erb"><a href="#BBS%2Fviews%2Fbadrequest.erb">BBS/views/badrequest.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/og7h7">https://rentry.co/og7h7</a></p> <h3 id="BBS/views/ca.erb"><a href="#BBS%2Fviews%2Fca.erb">BBS/views/ca.erb</a></h3> <p>コードへのリンクコードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/nh37f">https://rentry.co/nh37f</a></p> <h3 id="BBSviews/created.erb"><a href="#BBSviews%2Fcreated.erb">BBSviews/created.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/x6ofg">https://rentry.co/x6ofg</a></p> <h3 id="BBS/views/board.erb"><a href="#BBS%2Fviews%2Fboard.erb">BBS/views/board.erb</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/tab3d">https://rentry.co/tab3d</a></p> <hr /> <h2 id="BBS/public Directory"><a href="#BBS%2Fpublic+Directory">BBS/public Directory</a></h2> <h3 id="BBS/public/css/style.css"><a href="#BBS%2Fpublic%2Fcss%2Fstyle.css">BBS/public/css/style.css</a></h3> <p>コードへのリンク<br /> <a target="_blank" rel="nofollow noopener" href="https://rentry.co/wbw99">https://rentry.co/wbw99</a></p> <hr /> <h2 id="BBS Directory"><a href="#BBS+Directory">BBS Directory</a></h2> <h3 id="BBS/bbs.rb"><a href="#BBS%2Fbbs.rb">BBS/bbs.rb</a></h3> <pre><code class="ruby">require 'digest/sha2' require 'active_record' require 'sinatra' set :environment, :production set :sessions, exprre_after: 70000, secret: 'assdffgfhjjkklaslllg' ActiveRecord::Base.establish_connection(:adapter=>'sqlite3',:database=>'bbsdata.db') class BBSdata < ActiveRecord::Base self.table_name = 'bbsdata' end class Account < ActiveRecord::Base self.table_name = 'accounts' end # keepers able to delete scum comments. class Keeper < ActiveRecord::Base self.table_name = 'keepers' end class Kickaccount < ActiveRecord::Base self.table_name = 'kickaccounts' end # html sanitizer helpers do def h(text) Rack::Utils.escape_html(text) end end get '/' do redirect '/login' # create account end get '/ca' do # create account erb :ca end get '/ca2' do # create account @message = "Change username.The username is already in use." erb :ca end post '/create' do @username = h(params[:username]) @username.gsub!("\"|\\","") @username.gsub!(/;/,"") rawpassword = h(params[:passwd]) rawpassword.gsub!("\"|\\","") rawpassword.gsub!(/;/,"") begin f = Account.find(@username) if f.id == @username puts "Change username.The username is already in use." redirect '/ca2' end rescue =>e puts "OK.Username has accepted." end lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) algorithm = "100" rand = Random.new spice = Digest::SHA256.hexdigest(rand.bytes(20)) hashed = Digest::SHA256.hexdigest(rawpassword + spice) ca = Account.new # create account ca.id = @username ca.salt = spice ca.hashed = hashed ca.algorithm = algorithm ca.save lock.close erb :created end get '/login' do erb :login end get '/created' do session[:username] = @username erb :created end get '/board' do @u_name = session[:username] if @u_name == nil redirect '/badrequest' end begin kicked_account = Kickaccount.all kicked_account.each do |kv| if kv.userid == @u_name puts "kick out: #{kv.userid}" redirect '/logout' end end rescue => e puts "error" end @admin = "" puts "you are #{@u_name}." begin roomkeeper = Keeper.find(@u_name) @admin = roomkeeper.id puts "you are roomleeper." rescue => e puts "you are not roomkeeper." end all_data = BBSdata.all if all_data.count == 0 @table = "<tr><td>This BBS is Empty.</td></tr>" else @table = "" kicked = [] all_data.reverse_each do |one| if one.k == 1 kicked << one.userid kicked.uniq! end n1 = one.userid.size n2 = one.entry.size str1 = "" str2 = "" n1.times do str1 += "■" end n2.times do str2 += "■" end @table = @table + "<tr>" @table = @table + "<td style=\"witdh: 5%\">#{one.id}</td>" if one.v == 0 @table = @table + "<td>[#{one.userid}]</td>" else @table = @table + "<td>[#{str1}]</td>" end @table = @table + "<td><font size=\"-2\">#{Time.at(one.writedata)}</font></td>" if (one.userid == @u_name && one.v == 0) || (@admin != "" && one.v == 0) @table = @table + "<td><form action=\"/delete\" method=\"post\">" @table = @table + "<input type=\"hidden\" name=\"id\" value=\"#{one.id}\">" @table = @table + "<input type=\"hidden\" name=\"_method\" value=\"delete\">" @table = @table + "<input type=\"submit\" value=\"Delete\"></form></td>" else @table = @table + "<td></td>" end @table = @table + "</tr>" if one.v == 0 @table = @table + "<tr><td style=\"word-break: break-word\" colspan=\"4\">#{one.entry}</td></tr>\n" else @table = @table + "<tr><td style=\"word-break: break-word\" colspan=\"4\">#{str2}</td></tr>\n" end if @admin != "" && @admin != one.userid && !kicked.include?(one.userid) @table = @table + "<tr><td><font size=\"-2\">#{one.addr}</font></td>" @table = @table + "<td><form action=\"/kick\" method=\"post\">" @table = @table + "<input type=\"hidden\" name=\"bbsdataid\" value=\"#{one.id}\">" @table = @table + "<input type=\"hidden\" name=\"_method\" value=\"delete\">" @table = @table + "<input type=\"submit\" value=\"Kick\"></form></td></tr>\n" elsif @admin != "" && @admin != one.userid && kicked.include?(one.userid) @table = @table + "<tr><td><font size=\"-2\">#{one.addr}</font></td>" @table = @table + "<td><form action=\"/kickback\" method=\"post\">" @table = @table + "<input type=\"hidden\" name=\"bbsdataid\" value=\"#{one.id}\">" @table = @table + "<input type=\"hidden\" name=\"_method\" value=\"delete\">" @table = @table + "<input type=\"submit\" value=\"Kickback\"></form></td></tr>\n" end @table = @table + "<tr><td colspan=\"4\">_____________________________________________________________________________</td></tr>" end end erb :board end post '/new' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) maxid = 0 begin last = BBSdata.last if last.id > maxid maxid = last.id end rescue =>e puts maxid end new_comment = BBSdata.new new_comment.id = maxid + 1 new_comment.userid = h(session[:username]) new_comment.entry = h(params[:entry]) new_comment.writedata = Time.now.to_i new_comment.v = 0 # invisible : 1 new_comment.addr = @env['REMOTE_ADDR'] new_comment.k = 0 # kicked : 1 new_comment.save lock.close redirect '/board' # return to BBS end delete '/kick' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) maxid = 0 begin last = Kickaccount.last if last.id > maxid maxid = last.id end rescue => e puts "maxid: #{maxid}" end kicked = Kickaccount.new kicked.id = maxid + 1 username = "" begin f1 = BBSdata.find(h(params[:bbsdataid])) kicked.userid = f1.userid username = f1.userid kicked.addr = f1.addr kicked.save rescue => e puts "f1 Error" end begin f2 = BBSdata.where(userid: "#{username}") f2.each do |matched_one| puts matched_one.id matched_one.k = 1 matched_one.save end rescue => e puts "f2 Error" end lock.close redirect '/board' end delete '/kickback' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) puts (h(params[:bbsdataid])) username = "" begin f1 = BBSdata.find(h(params[:bbsdataid])) username = f1.userid begin f2 = Kickaccount.all f2.each do |matched_one| if matched_one.userid == f1.userid matched_one.destroy end end rescue => e puts "f2 Error" end rescue => e puts "f1 Error" end begin f3 = BBSdata.where(userid: "#{username}") f3.each do |m| puts m.id m.k = 0 m.save end rescue => e puts "f3 Error" end lock.close redirect '/board' end delete '/delete' do lock = File.open("lockfile","r+") lock.flock(File::LOCK_EX) begin s = BBSdata.find(h(params[:id])) # s.destroy s.v = 1 # invisible s.save rescue => e puts "Delete Error" end lock.close redirect '/board' end get '/logout' do session.clear erb :logout end get '/loginfail' do session.clear erb :loginfail end ################################# get '/*' do redirect '/' end ################################# #-------------------------------- post '/auth' do user = h(params[:uname]) user.gsub!("\"","") user.gsub!(/;/,"") pass = h(params[:pass]) pass.gsub!("\"","") pass.gsub!(/;/,"") redirect_flag = checkFlag(user, pass) if redirect_flag == "true" session[:username] = user redirect '/board' end redirect '/loginfail' end def checkFlag(check_username,check_password) redirect_flag = "fail" # go to "/loginfail" begin cf = Account.find(check_username) db_username = cf.id db_spice = cf.salt db_hashed = cf.hashed check_hashed = Digest::SHA256.hexdigest(check_password + db_spice) if check_hashed == db_hashed redirect_flag = "true" puts "go to \"/board\"" end rescue => e redirect_flag = "error" puts "go to \"/loginfail\"" end return(redirect_flag) end </code></pre> <h3 id="BBS/bbsdata.db"><a href="#BBS%2Fbbsdata.db">BBS/bbsdata.db</a></h3> <h4 id="initdb scripts"><a href="#initdb+scripts">initdb scripts</a></h4> <pre><code>create table bbsdata ( id integer primary key, userid varchar(20), entry varchar(150), writedata integer, v integer, addr varchar(20), k integer ); create table accounts ( id char(20) primary key, salt varchar(40), hashed varchar(70), algorithm char(5) ); create table keepers ( id char(20) primary key, salt varchar(40), hashed varchar(70), algorithm char(5) ); create table kickaccounts ( id integer primary key, userid char(20), addr varchar(20) ); </code></pre> <p>データベースのテーブル雛型をつくる。</p> <pre><code>~/BBS$ sqliter3 bbsdata.db < initdb </code></pre> tomato tag:crieit.net,2005:PublicArticle/18276 2022-08-12T02:30:19+09:00 2022-08-19T02:56:39+09:00 https://crieit.net/posts/Ruby-Sinatra-web できるだけわかりやすい Ruby + Sinatra で web アプリ <p><a target="_blank" rel="nofollow noopener" href="https://rentry.co/u5pcm">https://rentry.co/u5pcm</a></p> <h2 id="Sinatra"><a href="#Sinatra">Sinatra</a></h2> <p>web アプリって何かよくわからなかったので、Sinatra に入門。</p> <p><a target="_blank" rel="nofollow noopener" href="https://sinatrarb.com/intro-ja.html">https://sinatrarb.com/intro-ja.html</a></p> <h2 id="web アプリ"><a href="#web+%E3%82%A2%E3%83%97%E3%83%AA">web アプリ</a></h2> <p>web サーバーを起ちあげて、ブラウザでアクセスして何らかの処理をしたりしなかったりするものを web アプリと呼ぶようだ。<br /> 処理をしたりしなかったりする部分をフレームワークで、決まりきったパターンでプログラムを書けるというジャンルにあり、そのなかでもシンプルなのが Sinatra らしい。<br /> python だと <a target="_blank" rel="nofollow noopener" href="https://qiita.com/dauuricus/items/6e67d352fb4ee551a62e">cherrypy</a> と似ているのかもしれない。perl だと <a target="_blank" rel="nofollow noopener" href="https://perldancer.org/">Dancer</a> というフレームワーク は sinatra にパッと見たところはよく似ている。</p> <h2 id="web アプリを作ってみる"><a href="#web+%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">web アプリを作ってみる</a></h2> <p>"webrick" "sinatra" を使って、もっとも単純な処理をする web アプリを作ってみる。もっとも単純な処理だから、基本的にとても短いコードになるので概要をつかむのにはいいはずだ。</p> <h3 id="所要時間: ゆっくりやってみて 30 分程度。"><a href="#%E6%89%80%E8%A6%81%E6%99%82%E9%96%93%3A+%E3%82%86%E3%81%A3%E3%81%8F%E3%82%8A%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6+30+%E5%88%86%E7%A8%8B%E5%BA%A6%E3%80%82">所要時間: ゆっくりやってみて 30 分程度。</a></h3> <p>( web アプリって何か全く知らない場合、どうなってるか理解するには 1 時間から 2 時間程度かかると思います。が、全く知らなくてもわかるので、そのうち。 )</p> <p>プロジェクトのディレクトリを作る。</p> <pre><code>~/HelloWorld $ mkdir HelloWorld </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://bundler.io/guides/getting_started.html#getting-started">bundler</a> のインストール。</p> <p>( bundler については、一体何するものなのかは、調べてみてください。python だと、<a target="_blank" rel="nofollow noopener" href="https://docs.python.org/ja/3/library/venv.html">venv</a> みたいなものかなぁ。わかってなくても、他の言語やってみたりしたらわかるかもしれない。)</p> <pre><code>~/HelloWorld $ bundle config set path 'vender/bundle' ~/HelloWorld $ gem update ~/HelloWorld $ gem install bundler </code></pre> <p>bundler<br /> <a target="_blank" rel="nofollow noopener" href="https://github-com.translate.goog/rubygems/bundler?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp">https://github.com/rubygems/bundler</a></p> <hr /> <p>プロジェクトのディレクトリに移動。</p> <pre><code>~/HelloWorld $ cd HelloWorld </code></pre> <pre><code>~/HelloWorld $ bundle init </code></pre> <p>Gemfile が作られる。( Go 言語の <code>go mod init</code> みたいな感じだなぁ。)</p> <pre><code>~/HelloWorld $ ls Gemfile </code></pre> <hr /> <p>Gemfile を編集する。</p> <pre><code>~/HelloWorld $ vim Gemfile </code></pre> <pre><code>#frozen_string_literal: true source "https://rubygems.org" #gem "rails" gem "webrick" gem "sinatra" </code></pre> <pre><code>~/HelloWorld $ bundle install </code></pre> <p>"webrick" と "sinatra" がインストールされる。この二つが依存しているプログラムも同時にインストールされる。</p> <blockquote> <p>WEBrick<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.ruby-lang.org/ja/latest/library/webrick.html">https://docs.ruby-lang.org/ja/latest/library/webrick.html</a><br /> 要約<br /> 汎用HTTPサーバーフレームワークです。HTTPサーバが簡単に作れます。<br /> WEBrick はサーブレットによって機能します。サーブレットとはサーバの機能をオブジェクト化したものです。ファイルを読み込んで返す・forkしてスクリプトを実行する・テンプレートを適用するなど、「サーバが行なっている様々なこと」を抽象化しオブジェクトにしたものがサーブレットです。サーブレットは WEBrick::HTTPServlet::AbstractServlet のサブクラスのインスタンスとして実装されます。<br /> WEBrick はセッション管理の機能を提供しません。<br /> NOTE: WEBrick は Ruby 3.0 で標準ライブラリから削除されました。Ruby 3.0 以降で WEBrick を使いたい場合は rubygems から利用してください。</p> </blockquote> <hr /> <p>views ディレクトリを作る。</p> <pre><code>~/HelloWorld $ mkdir views </code></pre> <p>hello.rb, layout.erb, index.erb<br /> 3 つファイルを作る。</p> <pre><code>~/HelloWorld $ vim hello.rb </code></pre> <p>views ディレクトリに、layout.erb, index.erb というファイルを作っていきます。</p> <pre><code>~/HelloWorld $ cd views ~/HelloWorld/views $ vim layout.erb </code></pre> <pre><code>~/HelloWorld/views $ vim index.erb </code></pre> <pre><code>. HelloWorld # ディレクトリ ├── hello.rb ├── views # ディレクトリ │ ├── index.erb │ └── layout.erb │ </code></pre> <p><a href="https://crieit.now.sh/upload_images/e433b0da290749a242d7fe9d937d062062f5312de1ca8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e433b0da290749a242d7fe9d937d062062f5312de1ca8.png?mw=700" alt="Hello sinatra." /></a></p> <pre><code>~/HelloWorld $ dir Gemfile Gemfile.lock hello.rb vender views ~/HelloWorld $ dir views index.erb layout.erb </code></pre> <p>こんな感じです。</p> <pre><code>. HelloWorld # ディレクトリ ├── hello.rb ├── views # ディレクトリ │ ├── index.erb │ └── layout.erb │ # [ bundle init ] したらばできるファイル ├── Gemfile # 編集するファイル # [ bundle install ] したらばできるファイル ├── nender # auto generated └── Gemfile.lock # auto generated </code></pre> <hr /> <h3 id="Web アプリの実行"><a href="#Web+%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E5%AE%9F%E8%A1%8C">Web アプリの実行</a></h3> <p>準備できたので、hello.rb を実行したい。<code>bundle exec ruby hello.rb</code> で WEBrick ウェブサーバーをたちあげる。</p> <pre><code>~/HelloWorld/views $ cd HelloWorld </code></pre> <pre><code>~/HelloWorld $ bundle exec ruby hello.rb [2022-08-12 02:07:49] INFO WEBrick 1.7.0 [2022-08-12 02:07:49] INFO ruby 3.1.2 (2022-04-12) [arm-linux-androideabi] == Sinatra (v2.2.1) has taken the stage on 4567 for production with backup from WEBrick [2022-08-12 02:07:49] INFO WEBrick::HTTPServer#start: pid=2090 port=4567 </code></pre> <p>chrome ブラウザーでアクセスする。<code>localhost:4567</code> とアドレスバーに入力して <code>Enter</code> キー。<code>localhost:4567</code> は、 <code>http://127.0.0.1:4567</code> のことです。4567 は sinatra のデフォルトのポートナンバーです。</p> <p><strong>アドレス http://127.0.0.1 のポートナンバー 4567 = localhost:4567</strong></p> <p><a href="https://crieit.now.sh/upload_images/bf1158c30b8630527bbe43453ed3233462f539a9f28b1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/bf1158c30b8630527bbe43453ed3233462f539a9f28b1.png?mw=700" alt="image" /></a></p> <hr /> <p>どうなっているか見ていくと、</p> <p>hello.rb の '/' の指し示すのは localhost へアクセスしてきた場合、それはすなわち <code>127.0.0.0/</code> で、index.html に相当するファイルへのアクセスということになる。<br /> すると、そこに get でアクセスしてきたら、erb : index を返しますと定義している。</p> <pre><code class="ruby">get '/' do erb :index end </code></pre> <p><code>:index</code> とは、layout.erb でひな型を用意されているものに、index.erb で用意されている内容が埋め込まれたもののようだ。</p> <p>layout.erb のなかの</p> <pre><code><%= yield %> </code></pre> <p>が、index.erb の</p> <pre><code>Hello Sinatra. </code></pre> <p>に置き換わっているものが <code>:index</code> にあたる。</p> <p><a href="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f63d17db05c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f63d17db05c.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f6375404dab.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fa24c967c33e1da58f3f7386dd1d5b6a62f6375404dab.png?mw=700" alt="image" /></a></p> <p>hello.rb が実行されると、layout.erb というのが、プロトタイプというか、テンプレのフォーマットで、そこに index.erb が読み込まれるという感じ。</p> <p><strong>get localhost:4567 で :index ( :index は、index.erb をテンプレートの layout.erb に流し込んだもの )</strong> という感じ。</p> <hr /> <h3 id="プログラムコード コピペ用。"><a href="#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%89+%E3%82%B3%E3%83%94%E3%83%9A%E7%94%A8%E3%80%82">プログラムコード コピペ用。</a></h3> <h5 id="HelloWorld/hello.rb"><a href="#HelloWorld%2Fhello.rb">HelloWorld/hello.rb</a></h5> <pre><code class="ruby">require 'sinatra' set :environment, :production get '/' do erb :index end </code></pre> <h5 id="HelloWorld/views/layout.erb"><a href="#HelloWorld%2Fviews%2Flayout.erb">HelloWorld/views/layout.erb</a></h5> <pre><code class="html"><html> <head> <title>Sinatra framework</title> </head> <body> <h1>hello sinatra</h1> <%= yield %> </body> </html> </code></pre> <h5 id="HelloWorld/views/index.erb"><a href="#HelloWorld%2Fviews%2Findex.erb">HelloWorld/views/index.erb</a></h5> <pre><code class="ruby">Hello Sinatra! </code></pre> <p>もういちどファイルとディレクトリの構図を登場させておきます。</p> <pre><code>. HelloWorld # ディレクトリ ├── hello.rb ├── views # ディレクトリ │ ├── index.erb │ └── layout.erb │ </code></pre> <p>web アプリの実行は、HelloWorld ディレクトリの hello.rb を実行します。</p> <pre><code>~/HelloWorld $ bundle exec ruby hello.rb </code></pre> <p>似たような Hello World が1分30秒で説明されている。</p> <p><a target="_blank" rel="nofollow noopener" href="https://youtu.be/MgEgTu6NnWg">youtube</a></p> tomato tag:crieit.net,2005:PublicArticle/17844 2021-12-11T12:03:55+09:00 2021-12-11T12:05:57+09:00 https://crieit.net/posts/libreoffice-calc-jruby-sinatra LibreOffice Calcのfodsファイルを読み書きするサンプルをweb API化してみた <p>これは <a target="_blank" rel="nofollow noopener" href="https://adventar.org/calendars/6425">LibreOffice Advent Calendar 2021</a> の11日目の記事です。</p> <p>↓これにちょろっと付け足して web API 化してみただけの記事です。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b88c89d763cf872db5a1">JRubyでLibreOffice Calcのfodsファイルを読み書きするサンプル 2021</a></p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><code>webapi-2021</code> ブランチ</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/libreoffice-jruby-sample/tree/webapi-2021">https://github.com/sonota88/libreoffice-jruby-sample/tree/webapi-2021</a></p> <p>Java 版でやろうかと考えていたのですが、億劫になって JRuby 版でやりました。 JRuby で Sinatra 使った方が速い。</p> <p>主な部分だけ抜き出すとこんな感じ。考えるのが面倒だったので REST ではなく RPC風で。普通に <a target="_blank" rel="nofollow noopener" href="http://sinatrarb.com/">Sinatra</a> を使ってるだけですね。あんまり書くことがない…… 😓</p> <pre><code class="ruby"># app.rb require "sinatra" require_relative "libo_calc" post "/calc" do file = params["file"] sheet_name = params["sheet"] data = {} Calc.open(file) do |doc| sheet = doc.get_sheet_by_name(sheet_name) case params["command"] when "cell_get" data = cell_get(sheet, params) when "cell_set" cell_set(sheet, params) doc.save() when "dump" data = dump(sheet) else raise "unsupported command" end end content_type :json JSON.pretty_generate(data) end </code></pre> <h1 id="動かし方"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9">動かし方</a></h1> <p>イメージをビルド</p> <pre><code>docker build \ --build-arg USER=$USER \ --build-arg GROUP=$(id -gn) \ -t my:libo-jruby-webapi-2021 . </code></pre> <p>APIサーバ起動</p> <pre><code>./docker_run.sh ./jruby.sh app.rb -o 0.0.0.0 </code></pre> <h1 id="curl で動作確認"><a href="#curl+%E3%81%A7%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D">curl で動作確認</a></h1> <p>ファイル、シート名、コマンド、パラメータを POST で渡します。</p> <pre><code class="bash">curl -XPOST 'http://localhost:4567/calc' \ -d 'file=./sample.fods' -d 'sheet=Sheet1' \ -d 'command=cell_set' \ -d 'col=0' -d 'row=2' -d "val=$(date "+%F_%T")" #=> {} curl -XPOST 'http://localhost:4567/calc' \ -d 'file=./sample.fods' -d 'sheet=Sheet1' \ -d 'command=cell_get' \ -d 'col=0' -d 'row=2' #=> { "val": "2021-12-11_11:03:20" } curl -XPOST 'http://localhost:4567/calc' \ -d 'file=./sample.fods' -d 'sheet=Sheet1' \ -d 'command=dump' #=> { "rows": [ [ "(0, 0) 日本語テキスト 2021-01-02 14:34:26 +0900", "b1" ], [ "a2", "(1, 1) 2021-01-02 14:34:26 +0900" ], [ "2021-12-11_11:03:20", "12.34" ], [ "", "0" ] ] } </code></pre> <p>読み書きできてます。</p> <h1 id="Ruby + Faraday"><a href="#Ruby+%2B+Faraday">Ruby + Faraday</a></h1> <p>適当な HTTP クライアントライブラリを使って試してみました。</p> <pre><code class="ruby">require "faraday" puts "cell_set =>" res = Faraday.post( "http://localhost:4567/calc", { file: "./sample.fods", sheet: "Sheet1", command: "cell_set", col: 0, row: 2, val: Time.now.to_s } ) puts res.body puts "cell_get =>" res = Faraday.post( "http://localhost:4567/calc", { file: "./sample.fods", sheet: "Sheet1", command: "cell_get", col: 0, row: 2 } ) puts res.body puts "dump =>" res = Faraday.post( "http://localhost:4567/calc", { file: "./sample.fods", sheet: "Sheet1", command: "dump" } ) puts res.body </code></pre> <pre><code class="bash">$ bundle exec ruby sample_webapi_client.rb cell_set => { } cell_get => { "val": "2021-12-11 11:09:49 +0900" } dump => { "rows": [ [ "(0, 0) 日本語テキスト 2021-01-02 14:34:26 +0900", "b1" ], [ "a2", "(1, 1) 2021-01-02 14:34:26 +0900" ], [ "2021-12-11 11:09:49 +0900", "12.34" ], [ "", "0" ] ] } </code></pre> <p>大丈夫ですね。普通ですね。</p> <hr /> <p>というわけで、任意の HTTP クライアントからセルの読み書きができるようになりました。この記事は以上です。</p> sonota486 tag:crieit.net,2005:PublicArticle/14798 2019-02-14T00:14:27+09:00 2019-02-14T09:19:55+09:00 https://crieit.net/posts/esa-io esa.ioの育て方の講演を観れなかったので、スライドから内容を紐解いてみる <p>はてなブックマークを見ていたら、とても気になるスライドがありました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://speakerdeck.com/fukayatsu/esa-dot-iofalseyu-tefang">https://speakerdeck.com/fukayatsu/esa-dot-iofalseyu-tefang</a></p> <p>「現地で直接見たかった!」と思えるくらい、興味深いスライドでした。</p> <p>残念ながら既にイベントは終了してしまったようですが、スライドだけでも色々と得られそうな点がありましたので、ブログにまとめてみます。</p> <h2 id="esa.ioとは?"><a href="#esa.io%E3%81%A8%E3%81%AF%EF%BC%9F">esa.ioとは?</a></h2> <p>まず初めに「esa.io」について簡単に紹介します。</p> <blockquote> <p>esaは「情報を育てる」という視点で作られた<br /> 自律的なチームのためのドキュメント共有サービスです。</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://esa.io/" target="_blank" rel="noopener">esa - 自律的なチームのための情報共有サービス</a></p> <p>私なりの言葉でコンセプトを表現してみるならば「情報を育てるためのツールです。マークダウンによる気軽な情報発信と、ボトムアップからの情報発信により、組織の活性化を実現します」となります。</p> <p>私のように、普段からマークダウンでブログを書いているようなユーザにとっては、かなり相性が良いサービスであることは間違いありません。</p> <h2 id="サービス規模によって選択する適材適所のインフラ構成"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E8%A6%8F%E6%A8%A1%E3%81%AB%E3%82%88%E3%81%A3%E3%81%A6%E9%81%B8%E6%8A%9E%E3%81%99%E3%82%8B%E9%81%A9%E6%9D%90%E9%81%A9%E6%89%80%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E6%A7%8B%E6%88%90">サービス規模によって選択する適材適所のインフラ構成</a></h2> <p>スライドの前半でまず印象に残ったのは、インフラ構成です。</p> <ul> <li>サービス稼働当初は、Herokuでサーバを立ち上げることによって、インフラの手間を極限まで削減し、サービス開発に注力する</li> <li>サービスが成長してきた段階で、ユーザ数と今後の拡大予測に見合ったインフラ(AWS)へと載せ替える</li> </ul> <p>インフラは最初にしっかりと組んでしまい、その後は不変であると思われがちですが、AWSも、RDSやElastiCacheなど複数のサービスを組み合わせると、コストが無視できないレベルに増えてきます。</p> <p>ユーザ数に見合った最適なインフラの実現は、コスト面でもインフラに割くリソース面でも、参考になりそうです。</p> <h2 id="サブシステムにSinatraを採用している"><a href="#%E3%82%B5%E3%83%96%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%ABSinatra%E3%82%92%E6%8E%A1%E7%94%A8%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B">サブシステムにSinatraを採用している</a></h2> <p>個人的に気になったのは、esaの本体ではないサブシステム(UMLコンテナ、決済コンテナ)に、Sinatraを採用しているところです。</p> <p>Railsはもちろん素晴らしいフレームワークですが、シンプルな機能のシステムには、個人的にはRailsは冗長であると思うのです。Sinatra + RubyGemsライブラリの組み合わせでも、十分にWebシステムを開発することはできます。</p> <p>私的には、アーキテクチャにおいてマイクロサービスが流行したように、今後は、マイクロフレームワークとライブラリを組み合わせるスタイルの開発が、主流になっていくのではないかと予想しています。</p> <p>マイクロフレームワークのほうが、動作が早かったり、身動きが取りやすかったり、適材適所にライブラリを選べるといった利点は、往々にしてあります。</p> <p>システム規模に合わせたフレームワーク選定が出来ているという点は、ぜひ見習いたいです。</p> <h2 id="カテゴリ機能の経路列挙モデル"><a href="#%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E6%A9%9F%E8%83%BD%E3%81%AE%E7%B5%8C%E8%B7%AF%E5%88%97%E6%8C%99%E3%83%A2%E3%83%87%E3%83%AB">カテゴリ機能の経路列挙モデル</a></h2> <p>私が思う「経路列挙モデル」の利点は、DBのテーブル構成、および問い合わせクエリのSQLがシンプルになるところです。</p> <ul> <li>DBのインデックス機能を活用しにくい</li> <li>経路上流の変更に弱い(上位カテゴリほど多くのレコードに登場するので、変更レコード数が多くなる)</li> </ul> <p>・・・のようなデメリットは考えられますが、カテゴリが爆発的に増えることは考えにくいので、シンプルな構成を選択したということでしょうか?</p> <p>経路列挙モデルを採用した理由がスライドには書いてなかったので、ここは現地で見たかったです!</p> <h2 id="外部公開機能"><a href="#%E5%A4%96%E9%83%A8%E5%85%AC%E9%96%8B%E6%A9%9F%E8%83%BD">外部公開機能</a></h2> <p>外部公開機能で注目すべき点は、静的ファイルを「S3 + CloudFront」で配信することで、完全に本体から切り離している点です。</p> <p>URLを知っていればアクセスできるという静的コンテンツであり、プログラムによるユーザ認証が不要であるというメリットを活かした、賢い構成です。</p> <h2 id="認証を独自で実装しない"><a href="#%E8%AA%8D%E8%A8%BC%E3%82%92%E7%8B%AC%E8%87%AA%E3%81%A7%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%AA%E3%81%84">認証を独自で実装しない</a></h2> <p>認証システムにGoogle OAuthを採用することで、ログイン周りの開発コストを節約しています。</p> <p>認証周りよりも、サービスの本質的な部分の開発に多く時間をあてたほうが、特に開発初期の段階では幸せになれそうです。私も個人サービスは開発したいと考えているので、その際には参考にさせていただきます。</p> <h2 id="スピード感のある開発体制"><a href="#%E3%82%B9%E3%83%94%E3%83%BC%E3%83%89%E6%84%9F%E3%81%AE%E3%81%82%E3%82%8B%E9%96%8B%E7%99%BA%E4%BD%93%E5%88%B6">スピード感のある開発体制</a></h2> <p>ユーザからフィードバックを受け取って、30分後には修正版をリリースすることを目指すという開発体制は、簡単に真似できるものではありません。</p> <p>「開発スケジュールもノルマもない」という点においては、スケジューリングや統制管理をする時間があったら、その時間もクリエイティブな活動(開発)にあてたほうが良いという意味で解釈しました。</p> <p>ユーザ志向の発想が根付いていれば、無理にスケジュールを切らなくても、自然と早く良いモノを出したいという欲求が生まれるわけですね。</p> <h2 id="さいごに"><a href="#%E3%81%95%E3%81%84%E3%81%94%E3%81%AB">さいごに</a></h2> <p>もしかしたら、今回のスライドに私が心を惹かれてしまった理由は、サービスの運営から開発・インフラまで、全てを見ることができるそのフルスタック力にあるのかもしれません。</p> <p>少し大げさな表現かもしれませんが、人月の神話に書いてありそうな「1000人の開発者よりも10人のスーパーエンジニア」である組織とは、こういう組織のことを言うのかもしれませんね。とても参考になるスライドでした。</p> <p>次回がもしあれば、ぜひとも現地で聞きたいので、まずはesaのtwitterをフォローすることから始めることにします。</p> このすみ