NB. server_core_jconsole.ijs
NB. server_core_jconsole_2023_10_31.ijs

NB. Yuji Suda
NB. g.ysuda@gmail.com
NB. encoding code: UTF-8

NB. jsocket_serverの汎用部分 (個々のアプリケーションで変更の必要はない)

NB. ====================================================================================================
NB. Jソケットライブラリーの導入
load 'socket'
coinsert 'jsocket'

NB. -------------------------
NB. --- support functions ---
NB. -------------------------

NB. ====================================================================================================
NB. ソケットライブラリーのリセット
reset_socket =: 3 : 0
sdcleanup ''
)

NB. ====================================================================================================
NB. クライアントからのメッセージ受信
sdrecv_one_msg =: 3 : 0

NB. sdrecv 関数でクライアントからのメッセージを受信
r  =. sdrecv SKSERVER, SDRECV_BUFFER_SIZE, 0

NB. sderror 関数でエラーコードの取得
er =. sderror c=. >{.r

NB. クライアントからのメッセージ全体を取得し、グローバル変数 data に代入
data =. >1{r

NB. エラーコードがソケット回線切断の場合、切断のイベントを通知
if. (er -:'ECONNRESET') do.
   NB. print 'クライアントの通信切断がありました '
       print 'client has disconneced'
end.
NB. print 'メッセージの受信が完了しました ', CRLF
    print 'client message has been received', CRLF
data  NB. 受信メッセージを返す
)

NB. ====================================================================================================
NB. メッセージの追加受信
sdrecv_one_more =: 3 : 0

NB. sdrecv 関数でクライアントからのメッセージを受信
r =. sdrecv SKSERVER, SDRECV_BUFFER_SIZE, 0

NB. エラーコードの抽出
er =. sderror c=. >{.r

NB. 受信メッセージを取り出しグローバル変数 data へ代入
data =. >1{r   

NB. 受信メッセージのモニター
    print '==========='
NB. print '追加で受信したデータは以下の通りです '
    print 'added client message is as follows'
    print '==========='
    print data
    print '===========', CRLF

NB.エラーコードの表示
if. (er -:'ECONNRESET') do.
NB. print 'クライアントの通信切断がありました '
    print 'client has disconnected'
end.
NB. print 'bodyパートの追加受信が完了しました ', CRLF
    print 'body part has been arrived', CRLF
data NB. 受信メッセージを返す
)

NB. ====================================================================================================
NB. jsocket_serverの記述
start_server =: 3 : 0

NB. サーバー情報の表示

NB. APPLICATION_NAME, DATEはグローバルとして定義済み
NB. PORT_FOR_SERVER, SDRECV_BUFFER_SIZEは
NB. この関数を起動する、start_socket_server で入力される

PORT_FOR_SERVER =: rff 'port_selected.txt'

NB. print 'jsocket による ウエブブラウザ用サーバーの開始 '
NB. print 'バージョン年月日  ： ', APPLICATION_NAME, ' ', APPLICATION_DATE
NB. print '通信用ポート番号  ： ', PORT_FOR_SERVER
NB. print '受信バッファサイズ： ', SDRECV_BUFFER_SIZE

    print 'jsocket server now starts'
    print ' version name and date: ', APPLICATION_NAME, ' ', APPLICATION_DATE
    print '           port number: ', PORT_FOR_SERVER
    print 'size of reciver buffer: ', SDRECV_BUFFER_SIZE

NB. ポート番号とバッファサイズはjsocketでは文字列から数値への変更が必要
PORT_FOR_SERVER =:    ". PORT_FOR_SERVER
SDRECV_BUFFER_SIZE =: ". SDRECV_BUFFER_SIZE

NB. 受信ループでゼロ長のメッセージが来た時にここに戻ってみる
label_skip.

RESPONSE_IMG_FLG =: 0  NB. this flag is always nil but app can set true for it during its execution

NB. ソケットの初期化 (ソケット使用開始前に必須の処理)
reset_socket 0 NB. 補助関数

NB. print 'ソケット情報がリセットされました '
    print 'jsocket has been reset'

NB. 接続用ソケットの新設
SKLISTEN =: 0 pick sdcheck sdsocket''

NB. print '接続用ソケット番号は',' ', (":SKLISTEN), ' ', 'です '
    print 'socket number for connection is',' ', (":SKLISTEN)

NB. 接続用ソケットをバインドするポートの再利用を可能にするコマンド
sdcheck sdsetsockopt SKLISTEN, SOL_SOCKET, SO_REUSEADDR, 1

NB. 接続用ソケットを指定されたポートにバインドする
sdcheck sdbind SKLISTEN ; AF_INET_jsocket_ ; '' ; PORT_FOR_SERVER

NB. print  '通信用ソケット', ' ', (":SKLISTEN), ' を指定ポート', ' '
NB. print (": PORT_FOR_SERVER), ' ', 'にバインドしました  '
    print  'connection socket', ' ', (":SKLISTEN), 'has been binded at the port ', ' '
    print (": PORT_FOR_SERVER)

NB. クライアントからの接続要求の待機開始
sdcheck sdlisten SKLISTEN , 1

NB. サーバー停止フラグのリセット
server_halt_flag =. 0

NB. 「クライアント接続待ち」、接続後の「受信待ち」のソケット関数はブロッキング関数のため、
NB. 一旦動作すると、プロセスを中断することが出来ないため、クライアントがサーバーを停止出来るよう
NB. このフラグを設置した （プロセスの中断はJbreak.exeで可能ではるが、、、）

NB. クライアントからの接続待ち無限ループ開始 （★）--->呼応する ループの終端にも（★）を記載した
while. 1  do.   

NB. print  'クライアントからの接続待ち状態（無限ループ）に入りました ' 
    print  'now in the loop of connection request'
NB. print  '待ち受けポート番号: ', ":PORT_FOR_SERVER
    print  'at the port: ', ":PORT_FOR_SERVER
NB. print  'クライアントは http://127.0.0.1:', (":PORT_FOR_SERVER), 'でアクセスして下さい '
    print  'please access at http://127.0.0.1:', (":PORT_FOR_SERVER)

    initialize_response_image_bmp 0 NB. reset response_image.bmp to 03x03_Orange.bmp

    NB. クライアントからの接続要求待機カウンターの初期化
    checkRep =. 0  

    NB. クライアントの接続要求を待機する
    while. 1  do.
        checkRep =. checkRep + 1
        firstItem =. 0 pick sdcheck sdselect ''
        selectStatus =. $ firstItem
        if. (selectStatus = 1) do.
           NB. print '接続リクエスト有無の確認回数：', ":checkRep
               print 'counter for checking connection request: ', ":checkRep
           NB. print '接続したポート番号: ', ":PORT_FOR_SERVER
               print 'connected at the port: ', ":PORT_FOR_SERVER
               break.  NB. クライアントとの接続が確認できたのでこのループを抜ける
        end.
    end.

    NB. クライアントとの接続完了

    NB. クライアントとの交信の為に、接続用ソケットとは別の通信用ソケットを作成する
    SKSERVER =: 0 pick sdcheck sdaccept SKLISTEN

    NB. print 'クライアントとの接続完了!'
        print 'connected to client!'
    NB. print '以後、メッセージ受信モードに入ります '
        print 'now goes into msg recieving mode'
    NB. print '受・送信用ソケット番号は',' ', (": SKSERVER), ' ', 'です '
        print 'at the socket number ',' ', (": SKSERVER)
    NB. print 'サーバーはクライアントからのメッセージを待ち続けます '
        print 'server is waiting a message from the client'
    NB. print '受信後、応答処理を行い、クライアントへメッセージを送信します '
        print 'on arrival of message, it will be processed and reply will be sent'
    NB. print 'クライアントが接続を切るか、サーバー停止リスエストをするまで、'
        print 'this loop continues until client disconnects or requests server halt'
    NB. print 'この無限ループを継続します '

    NB. クライアントからのブラウザアクセスは HTTPプロトコルのGET methodで行わるが、
    NB. ブラウザによっては、GET methodを連発してくるものがあるので、その計数の為の変数を初期化する
    GETcount =. 0 
 
    NB. ここからメッセージ受信・処理・応答返信メッセージ送信の無限ループ開始
    
    while. 1  do.  NB. （★★） 無限ループの開始 --> 呼応するループの終端にも（★★）を記載した

       NB. print '通信ポート：',(":PORT_FOR_SERVER),' ','ソケット番号： '
           print 'port number: ',(":PORT_FOR_SERVER),' ','socket number: '
       NB. print (":SKSERVER),' ','で受信送信無限ループに入りました '
           print (":SKSERVER),' ',' is now in the rec/send loop'
       NB. print 'sdselect のモニタリング' 
           print 'sdselect status is as follows'

       NB. print '第１要素 (socket for read)    => ', ": 0 pick sdcheck sdselect ''  
           print 'first  element (socket for read)    => ', ": 0 pick sdcheck sdselect ''  
       NB. print '第２要素 (socket for send)    => ', ": 1 pick sdcheck sdselect ''  
           print 'second element (socket for send)    => ', ": 1 pick sdcheck sdselect ''  
       NB. print '第３要素 (socket with error ) => ', ": 2 pick sdcheck sdselect ''
           print 'third  element (socket with error ) => ', ": 2 pick sdcheck sdselect ''

       NB. 受信したメッセージを MSG_FROM_CLIENT に代入する
       MSG_FROM_CLIENT =: sdrecv_one_msg 0 NB. 補助関数

       NB. client message arrived
       TIME_ON_ARRIVAL =: current_min_and_sec_in_msec 0
       print 'TIME_ON_ARRIVAL = ', 20j0":TIME_ON_ARRIVAL
       
       NB. print '受信したメッセージのバイト数： ' , (": $ MSG_FROM_CLIENT), ' Byte(s)'
           print 'recieved message total byte: ' , (": $ MSG_FROM_CLIENT), ' Byte(s)'

       NB. 受信メッセージのモニター表示
           print '==========='
       NB. print 'Client -> Server へのメッセージ'
           print 'Client -> Server'
           print '==========='
           print  MSG_FROM_CLIENT
           print '==========='
    
       NB. 受信メッセージの長さがゼロか否かのチェック
       if. (($ MSG_FROM_CLIENT) = 0) do.
           NB. クライアントが接続を切ると長さがゼロの受信データとして戻るので、
           NB. 受信の無限ループを抜けて、一つ上位の接続待ちの無限ループに戻る
           NB. print '受信データサイズがゼロですので通信ループを抜けて接続待ちループに戻ります '
               print 'recived message size is zero so exit from rec/send loop and goes to connect loop'
           NB. break.
           NB. print '受信データゼロの時、ループ離脱でなくサーバースタートに戻ってみる'
	       print 'change from loop exit to the beging of server process'
	       goto_skip. 
       end.

       NB. 有限長の受信メッセージが届いたのでその解析をする

       NB. まず、メッセージの最初の４文字を取得
       NB. HTTPプロトコルの仕様ではブラウザの最初のメッセージは "GET "の４文字で始まる 
       NB. 一方、HTMLフォーム文書から送信した時はメッセージの先頭は"POST"となる 

       first4Char =. 4{.MSG_FROM_CLIENT
       print 'first4char is: ', first4Char

       NB. 次にメッセージの最後の４文字を取得

       NB. 最初の４文字が POST であれば、フォームの送信であるが、
       NB. HTTP headerとbodyが別の送信の場合があり、
       NB. その場合は最終４文字が CRLF CRLFなので、後続の通信を受信し
       NB. 合体させて、クライアントからのHTTPメッセージを完成させる 

       NB. (注）macのサファリで起こる現象への対策用

       last4Char =. _4{.MSG_FROM_CLIENT
       print 'last4Char    is: ', last4Char
       print 'last4Char code : ', (": a.i. last4Char), CRLF
       
       if. (first4Char -: 'POST') do.
           NB. add access method global
	   ACCESS_METHOD =: 'POST'   NB. to be used in transform def line of html for dropdown, checklist, radiobutton selected status
           if. (last4Char -: (CRLF, CRLF)) do.

               NB. bodyが別送の場合は最終４文字が CRLF, CRLF
               NB. 後続の通信を受信し合体させる

               NB. print ' HTTPのbodyデータがありませんので、続けて受信します '
                   print ' body of HTTP is missing, so contiues to recieve'
                   additional_msg_from_client =. sdrecv_one_more 0 NB. 補助関数

               NB. 追加受信データを合体し、HTTPメッセージとして完成させる 
                   MSG_FROM_CLIENT =: MSG_FROM_CLIENT, additional_msg_from_client
           end.
       end.

       NB. 受信メッセージからフォーム部品のvalueを抽出

       NB. メッセージの最初の４文字を再度取得

       first4Char =. 4{.MSG_FROM_CLIENT
       print 'first4char is: ', first4Char
       
       if. (first4Char -: 'JCMD') do.
           NB. print 'クライアントがHTTPプロトコル "JCMD"で接続して来ました '
               print 'client is sending JCMD protocol'

           NB. サーバーからクライアントへのHTTP応答メッセージの作成
	   a_return_msg_for_jcmd =. prepare_string_response_message_to_JCMD_method MSG_FROM_CLIENT
	   
	   if. a_return_msg_for_jcmd -: 'quit' do.
	         server_halt_flag =. 1  NB.server_halt_flag
             NB. server_response_message =. 'クライアントが quit をリクエストしました。', CRLF
                 server_response_message =. 'client requests quit server', CRLF
                 break.
	   else.
             server_response_message =. 'the following JCMD msg is received and processed: ', MSG_FROM_CLIENT, CRLF
	     server_response_message =. server_response_message, a_return_msg_for_jcmd
	   end.
	   
       end.
       
       if. (first4Char -: 'GET ') do.
           NB. add access method global
	   ACCESS_METHOD =: 'GET'   NB. to be used in transform def line of html for dropdown, checklist, radiobutton selected status
           NB. print 'クライアントがHTTPプロトコル "GET "で接続して来ました '
               print 'client uses http protocol GET message'
	       
	   NB. check User-Agent of the browser Firefox
	   a_user_agent =. 'User-Agent' extract_specific_line_with_x_str_and_return_result  MSG_FROM_CLIENT
	   browser_firefox =. $ 'Firefox' exclude_specific_line_with_x_str_and_return_result a_user_agent
	   if. browser_firefox = 0 do. USER_AGENT_FIREFOX =:1 else. USER_AGENT_FIREFOX =: 0 end.
	   
	   NB. check GET request item
	   a_request_item =. 1 pick_up_x_th_item_from_space_separated_str_y take_one_line MSG_FROM_CLIENT
           print 'GET request item --> ', a_request_item
           NB. サーバーからクライアントへのHTTP応答メッセージの作成
	   if. a_request_item -: '/response_image.bmp' do.
	      print 'request item is image .. and prepare http message for image transer', CRLF
	      server_response_message =. prepare_http_response_message_to_GET_response_image_bmp_request 0
	   else.
              server_response_message =. prepare_http_response_message_to_GET_method 0
	   end.
	   
           NB. server_response_message =. prepare_http_response_message_to_GET_method 0

           NB. ブラウザによっては GETを連発してくるものがあるため、その回数を
           NB. モニタする 
           GETcount =. GETcount + 1
           NB. print 'http GET メッセージ受信回数: ',(":GETcount), CRLF
               print 'http GET message rec counter: ',(":GETcount), CRLF
       end.

       if. (first4Char -: 'POST') do.
           NB. add access method global
	   ACCESS_METHOD =: 'POST'   NB. to be used in transform def line of html for dropdown, checklist, radiobutton selected status

           NB. フォーム部品のvalueを抽出
           extract_POSTed_values 0
	   
           NB. フォームで送信されたデータの抽出後
           NB. 最初に、クライアントがサーバー停止をリクエストしたかどうかをチェックする

           NB. ラジオボタンID radio99の値 value として"stop_server"
           NB. が装備されてる 
           NB. サーバー自体が動作を停止することが出来ない為、デバッグ用に
           NB. クライアントがサーバーを停止する仕様にした 
           NB. (サーバーがハングした場合はJBreak.exeを使えば停止はできるが、、)

           if. (RADIO99_VALUE -: 'stop_server') do.
              server_halt_flag =. 1
              NB. クライアントがサーバー停止をリクエストしたので、フラグをセットして、ここの無限ループを抜ける 
              break.  
           end.
           if. (RADIO_99_VALUE -: 'stop_server') do.
              server_halt_flag =. 1
              NB. クライアントがサーバー停止をリクエストしたので、フラグをセットして、ここの無限ループを抜ける 
              break.  
           end.

           NB. サーバー停止のリクエストが無ければ、
           NB. 抽出したデータを使って所望のＪ関数を実行する 

           execute_your_job 0

           NB. サーバーからクライアントへの応答メッセージの作成
           server_response_message =. prepare_http_response_message_to_POST_method 0
	   
           NB. クライアントからのデータ抽出、J関数の実行、応答メッセージの完成
       end. 

       NB. no protocol will be just echo backed
       if. (+/ first4Char E. 'GET POSTJCMD') = 0 do.
           print 'No protocol is found!!!', CRLF
           server_response_message =. 'Server Echo Back your message: ', MSG_FROM_CLIENT, CRLF
       end. 

       NB. 以上でGET_method或いはPOST_methodに応じたクライアントへの返送HTTPメッセージが完成
       NB. 通信用ソケットから送信する
       NB. avoid domain error 出来るか？  seems working good
       DEBUG_MSG =: server_response_message
       label_tryresend.
       try.
         sdcheck server_response_message sdsend SKSERVER , 0
       catch.
      NB.server_response_message =. CRLF, 'Domain error が sdsend で発生しました.', CRLF
         server_response_message =. CRLF, 'Domain error has occured in sdsend', CRLF
         server_response_message =. server_response_message ,'in the sdcheck server_response_message sdsend SKSERVER , 0', CRLF
         print server_respnse_message
         server_respnse_message =. DEBUG_MSG
         print server_respnse_message
         print 'catch end.....!!!!'
         NB.goto_tryresend.
       end.
       
       NB. server response msg has been sent via socket
       TIME_AT_END_OF_SEND_MSG =: current_min_and_sec_in_msec 0
       print 'TIME_AT_END_OF_SEND_MSG = ', 20j0":TIME_AT_END_OF_SEND_MSG

       NB. 以下は送信メッセージのモニター
           print '==========='
       NB. print 'Server -> Client へのメッセージ'
           print 'Server -> Client'
           print '==========='
           print server_response_message
           print '==========='

       NB. クライアントへ応答メッセージを送信後、クライアントからの次のメッセージ
       NB. を受信する無限ループの先頭に戻る 

       NB. server response msg has been displayed locally after sending
       TIME_AT_END_OF_DISPLAY_MSG =: current_min_and_sec_in_msec 0
       print 'TIME_AT_END_OF_DISPLAY_MSG = ', 20j0":TIME_AT_END_OF_DISPLAY_MSG

           print CRLF
       NB. print 'クライアントからのメッセージを受信した時刻 (msec): ', 10j0":TIME_ON_ARRIVAL
           print 'time at reciving msg    (msec): ', 10j0":TIME_ON_ARRIVAL
       NB. print 'メッセージを処理して応答を送信終了した時刻 (msec): ', 10j0":TIME_AT_END_OF_SEND_MSG
           print 'time at sending msg     (msec): ', 10j0":TIME_AT_END_OF_SEND_MSG
       NB. print 'ローカル画面モニターへの表示を完了した時刻 (msec): ', 10j0":TIME_AT_END_OF_DISPLAY_MSG
           print 'time needed for display (msec): ', 10j0":TIME_AT_END_OF_DISPLAY_MSG
           print CRLF
       NB. print '   メッセージ受信から応答送信迄の所要時間 (msec) : ', 10j0":(TIME_AT_END_OF_SEND_MSG - TIME_ON_ARRIVAL)
           print '   duration from rec to send            (msec): ', 10j0":(TIME_AT_END_OF_SEND_MSG - TIME_ON_ARRIVAL)
       NB. print '   応答メッセージのモニター表示の所要時間 (msec) : ', 10j0":(TIME_AT_END_OF_DISPLAY_MSG - TIME_AT_END_OF_SEND
           print '   duration from send to end of display (msec): ', 10j0":(TIME_AT_END_OF_DISPLAY_MSG - TIME_AT_END_OF_SEND_MSG)
           print CRLF

  end. NB. （★★）このend. は （★★）印の受信、応答作成、ソケット送信無限ループの end.

  NB. クライアントからサーバー停止のリクエストが届いた場合は
  NB. server_halt_flagがセットされて、ループを抜けてこの位置に来るので
  NB. そのフラッグ判定する

  if. (first4Char -: 'JCMD') *. (server_halt_flag = 1) do.
       NB. print 'クライアントがHTTPプロトコル "JCMD"で接続し、quitを要求して来ました '
           print 'client request quit via JCMD protocol'
       NB. print 'サーバーを停止します'
           print 'server will halt'
           break.
  end.

  if. (server_halt_flag = 1) do.
    NB. print 'クライアントからサーバー停止のリクエストがありましたのでサーバーを停止します '
        print 'server will halt on request from client'
    NB. (繰り返しになるがサーバーがハングした場合はJBreakを使えば停止できる)
    NB. クライアントによるサーバー停止コマンドの通知を受けてサーバーを停止させる
    break.
    NB. このbreakで、接続待機の無限ループを抜けてサーバー停止となる   
  end.
  
end.  NB. （★）この end. は 接続待機の無限ループ （★）印 の end.

NB. サーバー停止リクエストで接続ループを抜けたので、アプリ終了処理へ進む 

NB. ソケット環境をクリーンアップして終了
sdcheck sdcleanup ''

NB. print 'クライアントのリクエストによりサーバーを停止しました ' 
    print 'server has stopped on client request' 
1
)

NB. -------------------
NB. --- main script ---
NB. -------------------

NB. ====================================================================================================
NB. 使用するボート番号と受信バッファサイズを指定して
NB. サーバー関数を起動する関数
start_socket_server =: 3 : 0

PORT_FOR_SERVER =: rff 'port_selected.txt'

NB. print '使用するポート番号：', PORT_FOR_SERVER
    print 'port number for connection: ', PORT_FOR_SERVER

NB. print '指定された受信バッファサイズ：', SDRECV_BUFFER_SIZE
    print 'specified rec buffer size: ', SDRECV_BUFFER_SIZE

". 'start_server 0'
)

NB. ====================================================================================================
NB. 別名定義
start =: start_socket_server

NB. End of file
