socket.ioのsocket.emit、socket.broadcast.emit、socket.on

公式: https://socket.io/
– socket.ioはNode.jsで動いている

$ npm install socket.io

### node.jsサーバー起動
node app

var app = require('http').createServer(handler),
	io = require('socket.io').listen(app),
	fs = require('fs');
app.listen(1337);

function handler(req, res){
	fs.readFile(__dirname + '/index.html', function(err, data){
		if(err) {
			res.writeHead(500);
			return res.end('Error');
		}
		res.writeHead(200);
		res.write(data);
		res.end();
	})
}

### socket.io
emitで送って、onで待ち受ける

index.html

<script
  src="https://code.jquery.com/jquery-3.4.1.min.js"
  integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
  crossorigin="anonymous"></script>
	<script src="/socket.io/socket.io.js/"></script>
	<script>
		$(function(){
			var socket = io.connect();
			// emit: イベント発信 (ブラウザ側)
			// on: イベント待ち受け (サーバ側)
			$('#myForm').submit(function(){
				socket.emit('emit_from_client', $('#msg').val());
			});
		});
	</script>

app.js

io.sockets.on('connection', function(socket){
	socket.on('emit_from_client', function(data){
		console.log(data);
	});
});

### serverからclientにemit
app.js

io.sockets.on('connection', function(socket){
	socket.on('emit_from_client', function(data){
		// console.log(data);
		socket.emit('emit_from_server', 'hello from server: ' + data);
	});
});

html

<ul id="logs"></ul>
<script>
		$(function(){
			var socket = io.connect();
			// emit: イベント発信 (ブラウザ側)
			// on: イベント待ち受け (サーバ側)
			$('#myForm').submit(function(e){
				// preventDefaultでsubmit後に遷移しないようにする
				e.preventDefault();
				socket.emit('emit_from_client', $('#msg').val());
			});
			socket.on('emit_from_server', function(data){
				$('#logs').append($('<li>').text(data));
			});
		});
	</script>

### socket.broadcast.emit
– socket.emitは接続しているsocketのみ
– socket.broadcast.emitは接続しているsocket以外
– io.sockets.emitは自分も含めたsockets

io.sockets.on('connection', function(socket){
	socket.on('emit_from_client', function(data){
		// console.log(data);
		// socket.emitは接続しているsocketのみ
		// socket.emit('emit_from_server', 'hello from server: ' + data);
		// socket.broadcast.emitは接続しているsocket以外
		socket.broadcast.emit('emit_from_server', 'hello from server: ' + data);
	});
});

dbに保存しない場合は、単にsocket.broadcast.emit、socket.onでデータ通信が出来るわけか。

ProxyPass、ProxyPassReverseを設定し、商用環境でexpressサーバに接続

## virtualhostで、ProxyPass、ProxyPassReverseを設定し、node appとすると、商用のドメインからでもexpressさーばに接続できるようになる。

vi /etc/httpd/conf.d/hoge.conf

<VirtualHost *:80>
DocumentRoot /var/www/html/hoge/express
ServerName www.target.com
ServerAlias target.com
CustomLog logs/target.com-access.log common
ErrorLog  logs/target.com-error.log
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
AddDefaultCharset UTF-8
<Directory "/var/www/html/hoge/express/">
AllowOverride All
</Directory>
</VirtualHost>

ProxyPassは転送
ProxyPassReverseはリダイレクト情報の書き換え

なるほど、expressサーバについて大分理解が深まった。expressサーバはapacheとは別ですね。
しかし、expressを使うところだけ、Jsonで取得するように、ユーザからのアクセスはProxyPassを使って転送させるって、アーキテクチャとしてどうなんだろうか。。。express単体のアプリケーションならいいが、フレームワークの中でexpressを使うってのは、仕組み的には可能だが、他の方法を探した方が建設的か。。。

商用環境でexpressを実行しようとすると…

まずサーバーにてnodeが入っていることを確認
$ node -v
$ npm -v

express install
$ sudo npm install express -g
$ sudo npm install -g express-generator
$ express -h
$ sudo npm install nodemon -g

続いて、ディレクトリにexpress install
$ git clone https://github.com/***/***.git
$ cd dir

### nodemon, node起動
$ nodemon app
Forbidden
You don’t have permission to access /express/ on this server.

$ node app
Forbidden
You don’t have permission to access /express/ on this server.

app.listen(3000);
console.log("server starting ... ")

コマンドラインでは、server starting …と表示されているので、Expressサーバーは起動するが、アクセスしようとすると403(Forbidden)

### IPの3000ポート
$ ifconfig
160.***.***.**:3000でアクセス
-> このサイトにアクセスできません

80番ポート経由でアクセスした場合に、内部の3000番ポートにアクセスできないからか。

expressのincludeでSyntaxError: Unexpected identifier in /*/index.ejs while compiling ejs

GET / 500 1.492 ms – 1426
SyntaxError: Unexpected identifier in /*/index.ejs while compiling ejs

If the above error is not helpful, you may want to try EJS-Lint:
https://github.com/RyanZim/EJS-Lint
Or, if you meant to create an async function, pass `async: true` as an option.

includeの有無でエラーが生じるので色々試したところ、エラーが消えました。
## before

<% include header.ejs %>
	<h1>Posts</h1>
	<ul>
		<% for (var i=0; i<posts.length; i++) { %>
			<li>
				<a href="/posts/<%= i %>"><%= posts&#91;i&#93;.title %></a>
			</li>
		<% } %>
	</ul>
<% include footer.ejs %>

## after

<% include('header.ejs') %>
	<h1>Posts</h1>
	<ul>
		<% for (var i=0; i<posts.length; i++) { %>
			<li>
				<a href="/posts/<%= i %>"><%= posts&#91;i&#93;.title %></a>
			</li>
		<% } %>
	</ul>
<% include('footer.ejs') %>

ページ数が増えれば増えるほど、includeを使わない手はないので、includeでエラーがあるとドキッとします。

express 2

Regular Expression

app.get('/items/:id([0-9]+)', function(req, res){
	res.send('item no: ' + req.params.id);
});

file

app.get('/hello.txt', function(req, res){
	res.sendfile(__dirname + '/public/hello.txt');
});

public folder読み込み

app.use(express.static(__dirname + '/public'));

– app.useで読み込む処理をmiddlewareと言う

var logger = require('morgan');
app.use(logger('dev'));
app.use(function(req, res, next){
	console.log('my custom middleware');
	next();
})

$ sudo npm install nodemon -g
$ nodemon app

$ npm install ejs

app.get('/', function(req, res){
	res.render('index');
});

app.get('/', function(req, res){
	res.render('index', {title: 'title'});
});
<body>
	<h1><%= title %></h1>
	hello from index ejs!
</body>
app.param('id', function(req, res, next, id){
	var users = ['yamda', 'nakamura', 'kobayashi'];
	req.params.name = users[id];
	next();
});
app.get('/hello/:id', function(req, res){
	res.send('hello ' + req.params.name);
})

app.get('/bye/:id', function(req, res){
	res.send('hello ' + req.params.name);
})

### Post

var bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
// ディレクトリ
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// middleware
// app.use(app.router);
var logger = require('morgan');
app.use(logger('dev'));
app.use(express.static(__dirname + '/public'));

app.get('/new', function(req, res){
	res.render('new');
});

app.post('/create', function(req, res){
  //
  	res.send(req.body.name);
})

view, routingの設計をServerサイドの言語ではなく、JavaScriptで書いていける。

express

what is ‘express’ ?
-> Fast, unopinionated, minimalist web framework for Node.js

$ sudo npm install express -g
$ sudo npm install -g express-generator
$ express -h

$ express sample
$ cd sample
$ npm install
$ DEBUG=sample:* npm start
http://192.168.33.10:3000/

app.js

var express = require('express'),
	app = express();

// app.use(app.router);

app.get('/', function(req, res){
	res.send('hello world');
});

app.get('/wine', function(req, res){
	res.send('JACOB\'S CREEK');
});

app.listen(3000);
console.log("server starting ... ")
app.get('/users/:name', function(req, res){
	res.send('hello' + req.params.name);
});
app.get('/users/:name?', function(req, res){
	if (req.params.name){
		res.send('hello ' + req.params.name);
	} else {
		res.send('hello nobady!');
	}
});

リクエストに対してルーティングをやっていることはわかるが、サーバーの記述がよくわからない。apacheのように、サーバーを立てているのか? 既存のサーバー環境(nginx, apache等)で動かす場合は、どういう記述になるのか?
-> Node.jsをサービス化(デーモン化)して、nginxまたはApacheからリバースプロキシで接続

リバースプロキシとは?
->特定のサーバへのリクエストが必ず通過するように設置されたプロキシサーバ

ん? いまいちよくわかりません。

Laravel + Ajaxでbladeの一部の値(ログインユーザ順のデータ)を自動更新させたい

– bladeの一部の値(ログインユーザ順のデータ)を自動更新させたい

## 初期
controller
-> 自分以外のユーザをログイン順に取得している。

$users = User::where('id', '!=', $user_id)->orderBy('last_login','DESC')->get();

– controllerでjson形式で返せるらしい
-> ルーティングでgetとpostに対応してajaxのメソッドに繋げます
## Route

Route::get('/hoge/new', 'HogesController@create');
Route::match(['get', 'post'], '/hoge/getuser/', 'HogesController@getUser');

## Controller

public function getUser(){
        $user_id = 1;
        $users = User::where('id', '!=', $user_id)->orderBy('last_login','DESC')->get(['id','name','hoge']);
        $json = ["userData" => $userData];
        return response()->json($json);
    }

## view
-setTimeoutで5秒ごとにgetメソッドでcontrollerからデータを取得する
-while( table.rows[ 0 ] ) table.deleteRow( 0 ); でtableのrowsを全て削除し、jsonデータを入れたtd, trをappendする

<table class="table" id="userList">
</table>
// 省略

<script>
$(function(){
			getData()
		})

		function getData(){
			$.ajaxSetup({
				headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
			});
			$.get({
				url: '/hoge/getuser',
				method: 'GET',
				dataType: 'json',
			})
			.done(function(data){
				
				var table = document.getElementById('userList');
				while( table.rows[ 0 ] ) table.deleteRow( 0 );
				var rows = "";
				for(var i = 0; i < data.userData.length; i++){
					rows += "<tr>";
					rows += "<td>";
					rows += "hoge";
					rows += "</td>";
					rows += "</tr>";
				}
				$("#userList").append(rows);
			})
			.fail(function(){

			})
			setTimeout("getData()", 5000);
		}
</script>

これ思ってたより複雑だな。jsonで持ってくる場合、hasOneやbelongsToのデータはController側で整形しないとダメか。

ajaxのjsonデータは、getで取得するURLを叩くと、jsonを見ることが可能で、デバッグには良いが、商用環境で->get() とするとセキュリティ上問題があるので、viewで使用するカラムのみ ->get([‘columName’]) で取得するようにする。

laravel whereで複数の一致条件 ~ a =A, b = B もしくは a = B, b = A

Controllerでa =A, b = B もしくは a = B, b = A を探したい時。

複数のwhereの場合は、whereを繋げれば良いのですが、複数のwhereのどちらかの場合は、where whereをorWhereで繋げれば良い。

$messages = Hotels::where(function($q) use ($id, $user_id){
            $q->where('user_id', $id)->where('second_id', $user_id);
        })->orWhere(function($q) use ($id, $user_id){
            $q->where('user_id', $user_id)->where('second_id', $id);
        })->get();

割と簡単に書けます。

Collectiveでのcheckboxの書き方

– view側でnameをブランケットで記述する。
– controllerのwhereInでチェックした値を呼び出せる

### blade

<div class="item">
	{!! Form::checkbox('category[]', 1, 1, ['class'=>'form-check-input']) !!}
	{!! Form::label('category[]', 'シティホテル', ['class'=>'form-check-label']) !!} 
</div>
<div class="item">
	{!! Form::checkbox('category[]', 2, 1, ['class'=>'form-check-input']) !!}
	{!! Form::label('category[]', 'ビジネスホテル', ['class'=>'form-check-label']) !!} 
</div>

### controller

if($request->has('category')){
            $Hotels = Message::where('id', $id)->whereIn('category_id', $request->input('category'))->orderBy('created_at','ASC')->paginate(2)->onEachSide(1);
        } else

input::all()の場合は、配列で渡ってくるので、そのままDBに流し込めば良い。
controller側で配列を作ろうとしたら、checkboxの最後の値しか渡ってこずに失敗しました。

onEachSide()が効かない時

bladeのpaginationでonEachSideを実装するも、Laravel6.xで全て表示されてしまう。

{{$users->onEachSide(1)->appends(request()->input())->render()}}
{{$users->appends(request()->input())->onEachSide(1)->render()}}

Controller側で、onEachSide()を付ける。

$users = User::orderBy('updated_at','DESC')->paginate(10)->onEachSide(1);

公式では、デフォルトで3つと書かれているが、機能しない。何故だか釈然としないが、controllerで対応する。
https://readouble.com/laravel/6.x/ja/pagination.html