4Dリクエストハンドラーで HTTPリクエストを管理する

今日の Web主導の世界では、サーバーは圧倒的な数のリクエストを処理します。これらのリクエストを効率的に分析し、カウントし、解釈し、再ルーティングすることは、特に MVC の原則を適用する場合に不可欠です。

4D 20 R8 では、4D HTTPサーバーHTTPリクエストハンドラーを導入したことにより、特定の URLパターンに対して、どのビジネスロジックをトリガーするかを正確に定義することができます。

この機能の主な利点は以下の通りです:

  • 受信リクエストを処理するコードをより細かく実装できます。このビジネスロジックは複数のクラスに分割できます。On Web Connection データベースメソッドに書いた、長々とした Case of 構文は忘れましょう。
  • Qodlyページ上でリダイレクトを可能にすることで、4Dアプリケーションを Webに拡張する新たな可能性を開きます。

 

この強力な機能は、以下に詳しく説明するように、多くの可能性を解き放ちます。さらに詳しく学び、具体的な例を使ってみましょう。

HDI: HTTPリクエストハンドラー

詳細に入る前に、この機能から得られるすべての利点を示す比較分析をしてみましょう。

比較分析

機能 on Web Connection データベースメソッドをハンドラーとして使用 HTTP ハンドラーを使用
リクエストを処理するコードを複数のクラスに分割する

X

O

Qodly ページでユーザーをリダイレクトする

X

O

存在する Webリソース (たとえば htmlページ) をリクエストする場合でも、ユーザーに認証を求める X O

詳しくみていくために、実現させたい動作を確認しましょう:

  • 認証されていないユーザーは認証ページにリダイレクトされます。
  • ユーザーは /pages フォルダーの htmlファイルしか見ることができません。このフォルダーに存在しないページをリクエストすると、Not foundページにリダイレクトされます。
  • 許可されていない他のリソースをリクエストすると、Not authorizedページにリダイレクトされます。
  • /pagesリクエストは /pages/welcome.html ページを表示します。
  • 許可されている htmlページでは、いくつかのファイルのアップロードとダウンロード機能を提供します。

 

On Web Authentication / Connection データベースメソッドで行う場合

On Web Authentication メソッド

Case of 
	// ユーザーが認証されていない場合
	: (Session.isGuest())
		WEB SEND HTTP REDIRECT("/authentication/authentication.html")
		return False
	Else 
		return True
End case

On Web connection メソッド

#DECLARE($url : Text)

Case of 
		
	: ($url="/pages")
		WEB SEND HTTP REDIRECT("/pages/welcome.html")
		
	: (Position("/fileUpload/"; $url)=1)
		// ファイルアップロードを扱います
		
	: (Position("/fileDownload"; $url)=1)
		// ファイルダウンロードを扱います
		
        // リクエストされたリソースが pagesフォルダーの htmlページでない場合
	: Not(Match regex("/pages/[a-z]*[0-9]*[.]html"; $url))
		WEB SEND HTTP REDIRECT("/error/notAuthorized.html")
	Else 
		WEB SEND HTTP REDIRECT("/error/notFound.html")
End case 

これは、On Web Connection データベースメソッドが複雑で、維持管理が難しいことを示すコードのサンプルです。

http リクエストハンドラーで行う場合

以下の一連の例では、いくつかの URLパターンを特定のビジネスロジックに簡単に関連付ける方法と、このコードをクラス間で適切に整理する方法を紹介します。

また、Qodlyページでリダイレクトを処理する方法や、(存在しているリソースをリクエストした場合でも) ユーザーを認証ページへリダイレクトする方法も学びます。

なぜリクエストハンドラーを使うのか?

リクエストハンドラーを扱うと、さまざまなシナリオが可能になります:

  • 特定の URL をリソースプロバイダーとして使う(例: その URL を呼び出してさまざまなファイルをダウンロードする)
  • 特定の URL をファイルアップロードボックスとして使う (例: その URL を呼び出して様々なファイルをアップロードする)
  • ビジネスコンテキストに応じて、リダイレクトを処理する (例: ユーザーの認証ステータスに応じた処理、ページが見つからない場合、適切な権限が付与されているかかどうか…など)。

HTTPリクエストハンドラーの設定

HTTPリクエストハンドラーは、Project/Sourcesフォルダー内の HTTPHandlers.jsonファイルにてセットアップする必要があります。

このファイルには JSON形式で、オブジェクトのコレクションが含まれています。各オブジェクトは、以下の情報を持つリクエストハンドラーを参照します:

  • 処理する URLパターン
  • URL で使用される動詞
  • リクエストハンドラーコードが実装されている [シングルトンクラス+関数]

 

HTTPHandlers.jsonファイル:

[
    {
        "class": "GeneralHandling",
        "method": "gettingStarted",
        "pattern": "start",
        "verbs": "get, post"
    }
]

上の例は、GET または POST動詞による /start/ で始まるリクエストがサーバーで受信されると、GeneralHandling シングルトンクラスの関数 gettingStarted が実行されることを意味します。

リクエストハンドラーの実装

上記のシングルトンクラス共有シングルトンでなくてはなりません。

入力: 4D.IncomingMessageクラスのインスタンス

リクエストは、4D.IncomingMessage クラスのオブジェクトインスタンスとしてサーバーで受信されます。

以下の情報が利用可能です:

  • リクエストの完全な URL
  • URL の構成要素
  • リクエストの HTTP動詞
  • リクエストのヘッダー
  • URL に含まれるパラメーター (あれば)
  • リクエストの本文 (あれば)

 

リクエストハンドラーはこれらの情報を使って、適切なビジネスロジックをトリガーすることができます。

出力: 4D.OutgoingMessageクラスのインスタンス

必要に応じて、リクエストハンドラーは 4D.OutgoingMessageクラスのオブジェクトインスタンス (ブラウザーが処理できる Webコンテンツ (ファイルなど)) を返すことができます。

簡単な例題

上と同じ HTTPHandlers.jsonファイルを使用します:

[
    {
        "class": "GeneralHandling",
        "method": "gettingStarted",
        "pattern": "start",
        "verbs": "get, post"
    }
]

http://127.0.0.1/start/example?param=demo&name=4D のリクエストはブラウザーで GET動詞で実行されます。このリクエストは、以下の GeneralHandling シングルトンクラスの gettingStarted() 関数によって処理されます:

shared singleton Class constructor()


Function gettingStarted($request : 4D.IncomingMessage) : 4D.OutgoingMessage

var $result:=4D.OutgoingMessage.new()
var $body : Text

$body:="Called URL: "+$request.url+"\n"

$body+="The parameters are received as an object:"+"\n"+JSON Stringify($request.urlQuery; *)+"\n"

$body+="The verb is: "+$request.verb+"\n"

$body+="There are "+String($request.urlPath.length)+" url parts - Url parts are: "+$request.urlPath.join(" - ")+"\n"+"\n"

$result.setBody($body)
$result.setHeader("Content-Type"; "text/plain")

return $result

リクエストは、サーバー上で $request (新しい 4D.IncomingMessage クラスのオブジェクトインスタンス) として受け取られます。

この $requestオブジェクトは次のプロパティを持ちます:

  • url (String) – リクエストの URL
  • urlQuery (Object) – リクエストのパラメーター。
  • verb (String) – HTTP動詞。
  • urlPath (文字列のコレクション) – リクエストの URL構成要素

 

これがレスポンスです:

リクエストの本文を処理する

新しい 4D.IncomingMessage クラスは、リクエストのヘッダーボディを取得するための関数を提供します。

例題

サーバー上にファイルをアップロードする簡単な例を実行してみましょう。

HTTPHandlers.jsonファイル:

[
    {
        "class": "UploadFile",
        "method": "uploadFile",
        "regexPattern": "/putFile",
        "verbs": "POST"
    }
]

URLパターンが regexPatternプロパティで正規表現として指定されていることに注意してください。

http://127.0.0.1:8044/putFile?fileName=testFile のリクエストは POST 動詞で実行され、ボディにファイルコンテンツがあります。

UploadFile シングルトンクラス:

Function uploadFile($request : 4D.IncomingMessage) : 4D.OutgoingMessage
	
var $response:=4D.OutgoingMessage.new()
var $body:="Not supported file"
var $fileName; $fileType : Text
var $file : 4D.File
var $picture : Picture
var $created : Boolean
	
// ファイル名は URL内のパラメーターで指定されています
$fileName:=$request.urlQuery.fileName
	
// "Content-Type" ヘッダーにより、本文のフォーマットが指定されています
$fileType:=$request.getHeader("Content-Type")
	
Case of 
	// 本文が PDFファイルの場合
	: ($fileType="application/pdf")
		$file:=File("/PACKAGE/Files/"+$fileName+".pdf")
		$created:=$file.create()
		// getBlob() 関数は、リクエスト本文を BLOB として返します
		$file.setContent($request.getBlob())
		$body:="Upload OK - File size: "+String($file.size)
			
	// 本文が JPG画像の場合
	: ($fileType="image/jpeg")
		$file:=File("/PACKAGE/Files/"+$fileName+".jpg")
		// getPicture() 関数はリクエスト本文をピクチャーとして返します
		$picture:=$request.getPicture()
		WRITE PICTURE FILE($file.platformPath; $picture)
		$body:="Upload OK - Image size: "+String($file.size)
End case 

$response.setBody($body)
$response.setHeader("Content-Type"; "TEXT/plain")
	
return $response

URLのパラメーター (fileName) として受け取ったファイル名は、リクエストの urlQuery オブジェクトで受け取ることができます。

リクエストのヘッダーとボディを取得する関数

上の例題では、リクエストのヘッダーを返す getHeader() 関数を使用しています。4D.IncomingMessage クラスは、すべてのリクエストヘッダーを含む headers プロパティ (オブジェクト) も提供します。

Content-Typeヘッダーは、受け取った本文のファイル形式を指定します。この例では、pdf と jpegフォーマットだけが扱われます。

$request オブジェクトの getBlob()getPicture() 関数に注目してください。これらは、Content-Typeヘッダーに合致する適切なフォーマットで本文のコンテンツを返します。

これらの関数の詳細については、ドキュメントを参照してください。

getText() 関数も使用可能で、リクエストの本文を文字列として返します (文字列として送信された場合)。

本文が有効な JSON表現として与えられた場合、getJSON() 関数は正しい型 (オブジェクト、コレクション、文字列など) でその解決を提供します。

ハンドラーによるリダイレクト

リクエストハンドラーは、4D.OutgoingMessage クラスのインスタンスを返すことで、特定のページにリダイレクトすることもできます。リダイレクトするページを示すために Locationヘッダーを使わなければなりません。

HTTP ステータス 3xx は、リクエストを完了させるためにクライアントが追加のアクションを取らなければならないことを示します

次のシナリオを実行してみましょう:

– 認証されたユーザーは、WebFolder/pages フォルダー内のすべての htmlページを表示できます。ユーザーが認証されていない場合は認証ページにリダイレクトされます。

– 要求されたページがこのフォルダーにない場合、Not foundページが提供されます。

– ユーザーが /pagesフォルダーのサブフォルダー内のページを要求した場合、Not authorizedページが提供されます。

HTTPHandlers.jsonファイル:

[
    {
        "class": "PagesHandling",
        "method": "handle",
        "regexPattern": "/pages/"
    }
]

PagesHandling シングルトンクラス:

shared singleton Class constructor()
	
Function handle($request : 4D.IncomingMessage) : 4D.OutgoingMessage
	
var $result:=4D.OutgoingMessage.new()
var $file : 4D.File
	
// ユーザーが認証されていない場合
If (Session.isGuest())
	$result.setHeader("Location"; "/authentication/Authentication.html")
	$result.setStatus(307)
Else 
		
	// /pages フォルダーの htmlページがリクエストされた場合
	If (($request.urlPath.length=2) && (Position(".html"; $request.url)#0))
			
		$file:=File("/PACKAGE/WebFolder"+$request.url)
			
		// ページが存在する場合
		If ($file.exists)
			$result.setBody($file.getContent())
			$result.setHeader("Content-Type"; "text/html")
			$result.setStatus(200)
				
		// ページが存在しない場合
		Else 
			$result.setHeader("Location"; "/error/NotFound.html")
			$result.setStatus(307)
		End if 
			
	// /pages フォルダーの htmlページではないリクエストがされた場合
	Else 
		$result.setHeader("Location"; "/error/NotAuthorized.html")
		$result.setStatus(307)
	End if 
End if 
	
return $result

リクエストハンドラーは、より複雑なビジネスロジックを可能にする Session オブジェクトにもアクセスすることができます (権限の存在をチェックするには、Session.storage 共有オブジェクトを使用します)。

この例題を、このブログ記事の冒頭で紹介した例 (On Web Connection データベースメソッドで処理されるリダイレクト) と比べてみてください。HTTPHandlers.json ファイルで指定された URLパターンと 4D.IncomingMessageクラスの urlPathプロパティのおかげで、URL を複雑な正規表現でテストする必要がないことがわかります。

qodly ページでリダイレクトするには?

Location ヘッダーに Qodly ページ の レンダリングURLを指定するだけです:

$result.setHeader("Location"; "https://myQodlyApp/$lib/renderer/?w=Authentication")

Download the HDI to practise handling the HTTP requests and don’t wait more to enter the project mode world proposing classes and other powerful features. 

HDI をダウンロードして HTTPリクエストの処理を練習してみてください。クラスやその他の強力な機能を提案するプロジェクトモードの世界に入るのをためらう必要はもうありません。

Avatar
- プロダクトオーナー - Marie-Sophie Landrieu-Yvertは、2017年にプロダクトオーナーとして4Dプロダクトチームに参加しました。プロダクトオーナーとして、彼女はユーザーストーリー(ユーザーが期待する新機能とその使用法)を書き、それを具体的な機能仕様に変換する役割を担っています。また彼女の役割は、実装された機能が顧客のニーズを満たしているかどうかを確認することでもあります。彼女は1995年にESIGELEC Engineering Schoolを卒業し、IBMでエンジニアとしてのキャリアをスタートさせました。様々なプロジェクト(保守や新規のプロジェクト)に参加し、Cobolのデベロッパーとして働きました。その後、UMLデザイナーおよびJavaデベロッパーとして勤務。最近は、機能要件の分析・記述、ビジネスチームと開発チームの調整などを主に担当しています。