///|
/// Network Types - CDP Network domain の内部表現
/// ブラウザの非同期IOレイヤーとの通信に使用
// =============================================================================
// Request ID
// =============================================================================
///|
/// ネットワークリクエストの一意識別子
pub(all) struct RequestId(Int) derive(Eq, Hash, Debug)
///|
/// ID generator
priv struct RequestIdGenerator {
mut next_id : Int
}
///|
let request_id_gen : RequestIdGenerator = { next_id: 0 }
///|
pub fn RequestId::next() -> RequestId {
let id = request_id_gen.next_id
request_id_gen.next_id += 1
RequestId(id)
}
///|
/// Reset request ID counter (for testing)
pub fn reset_request_id_counter() -> Unit {
request_id_gen.next_id = 0
}
// =============================================================================
// HTTP Method
// =============================================================================
///|
/// HTTP メソッド
pub(all) enum HttpMethod {
GET
POST
PUT
DELETE
HEAD
OPTIONS
PATCH
} derive(Eq, Debug)
///|
pub fn HttpMethod::from_string(s : String) -> HttpMethod {
match s.to_upper() {
"GET" => GET
"POST" => POST
"PUT" => PUT
"DELETE" => DELETE
"HEAD" => HEAD
"OPTIONS" => OPTIONS
"PATCH" => PATCH
_ => GET
}
}
///|
pub fn HttpMethod::to_string(self : HttpMethod) -> String {
match self {
GET => "GET"
POST => "POST"
PUT => "PUT"
DELETE => "DELETE"
HEAD => "HEAD"
OPTIONS => "OPTIONS"
PATCH => "PATCH"
}
}
// =============================================================================
// Network Request
// =============================================================================
///|
/// ネットワークリクエスト
pub(all) struct NetworkRequest {
/// リクエストID
id : RequestId
/// リクエストURL
url : String
/// HTTP メソッド
http_method : HttpMethod
/// リソースタイプ
resource_type : ResourceType
/// リクエストヘッダー
headers : Map[String, String]
/// ドキュメントURL(リクエスト元)
document_url : String
/// フレームID
frame_id : String
/// ローダーID
loader_id : String
/// タイムスタンプ(ミリ秒)
timestamp : Double
} derive(Debug)
///|
pub fn NetworkRequest::new(
url : String,
resource_type : ResourceType,
document_url? : String = "",
frame_id? : String = "main",
http_method? : HttpMethod = GET,
) -> NetworkRequest {
let id = RequestId::next()
{
id,
url,
http_method,
resource_type,
headers: {},
document_url,
frame_id,
loader_id: "loader-\{id}",
timestamp: 0.0,
}
}
///|
pub fn NetworkRequest::with_headers(
self : NetworkRequest,
headers : Map[String, String],
) -> NetworkRequest {
{ ..self, headers, }
}
///|
pub fn NetworkRequest::with_timestamp(
self : NetworkRequest,
timestamp : Double,
) -> NetworkRequest {
{ ..self, timestamp, }
}
///|
pub fn NetworkRequest::with_loader_id(
self : NetworkRequest,
loader_id : String,
) -> NetworkRequest {
{ ..self, loader_id, }
}
// =============================================================================
// Network Response
// =============================================================================
///|
/// ネットワークレスポンス
pub(all) struct NetworkResponse {
/// 対応するリクエストID
request_id : RequestId
/// レスポンスURL(リダイレクト後の最終URL)
url : String
/// HTTP ステータスコード
status : Int
/// ステータステキスト
status_text : String
/// MIME タイプ
mime_type : String
/// レスポンスヘッダー
headers : Map[String, String]
/// レスポンスボディ(オプショナル)
body : String?
/// コンテンツ長
content_length : Int
/// タイムスタンプ(ミリ秒)
timestamp : Double
/// ローダーID(オプショナル)
loader_id : String
} derive(Debug)
///|
pub fn NetworkResponse::new(
request_id : RequestId,
url : String,
status : Int,
) -> NetworkResponse {
let RequestId(id) = request_id
{
request_id,
url,
status,
status_text: status_to_text(status),
mime_type: "text/html",
headers: {},
body: None,
content_length: 0,
timestamp: 0.0,
loader_id: "loader-\{id}",
}
}
///|
fn status_to_text(status : Int) -> String {
match status {
200 => "OK"
201 => "Created"
204 => "No Content"
301 => "Moved Permanently"
302 => "Found"
304 => "Not Modified"
400 => "Bad Request"
401 => "Unauthorized"
403 => "Forbidden"
404 => "Not Found"
500 => "Internal Server Error"
502 => "Bad Gateway"
503 => "Service Unavailable"
_ => "Unknown"
}
}
///|
pub fn NetworkResponse::with_body(
self : NetworkResponse,
body : String,
) -> NetworkResponse {
{ ..self, body: Some(body), content_length: body.length() }
}
///|
pub fn NetworkResponse::with_headers(
self : NetworkResponse,
headers : Map[String, String],
) -> NetworkResponse {
{ ..self, headers, }
}
///|
pub fn NetworkResponse::with_mime_type(
self : NetworkResponse,
mime_type : String,
) -> NetworkResponse {
{ ..self, mime_type, }
}
///|
pub fn NetworkResponse::with_timestamp(
self : NetworkResponse,
timestamp : Double,
) -> NetworkResponse {
{ ..self, timestamp, }
}
///|
pub fn NetworkResponse::with_loader_id(
self : NetworkResponse,
loader_id : String,
) -> NetworkResponse {
{ ..self, loader_id, }
}
// =============================================================================
// Network Events (CDP互換)
// =============================================================================
///|
/// ネットワークイベント - CDP Network domain のイベントに対応
pub(all) enum NetworkEvent {
/// リクエスト送信前
RequestWillBeSent(NetworkRequest)
/// レスポンス受信
ResponseReceived(NetworkResponse)
/// ロード完了
LoadingFinished(request_id~ : RequestId, encoded_data_length~ : Int)
/// ロード失敗
LoadingFailed(request_id~ : RequestId, error~ : String)
} derive(Debug)
///|
pub fn NetworkEvent::request_id(self : NetworkEvent) -> RequestId {
match self {
RequestWillBeSent(req) => req.id
ResponseReceived(res) => res.request_id
LoadingFinished(request_id~, ..) => request_id
LoadingFailed(request_id~, ..) => request_id
}
}
// =============================================================================
// Fetch Task Result
// =============================================================================
///|
/// FetchResource タスクの結果
pub(all) enum FetchResult {
/// 成功
Success(response~ : NetworkResponse)
/// リダイレクト
Redirect(location~ : String, status~ : Int)
/// 失敗
Failure(error~ : String)
} derive(Debug)
///|
pub impl Show for RequestId with fn output(self, logger) {
logger.write_string("RequestId(\{self.0})")
}
///|
pub impl Show for HttpMethod with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
pub impl Show for NetworkRequest with fn output(self, logger) {
logger.write_string("{id: \{self.id}, url: ")
logger.write_string("\"")
logger.write_string(self.url)
logger.write_string("\"")
logger.write_string(", http_method: \{self.http_method}, ")
logger.write_string("resource_type: \{self.resource_type}, headers: ")
logger.write_object(to_repr(self.headers))
logger.write_string(", ")
logger.write_string("document_url: ")
logger.write_string("\"")
logger.write_string(self.document_url)
logger.write_string("\"")
logger.write_string(", frame_id: ")
logger.write_string("\"")
logger.write_string(self.frame_id)
logger.write_string("\"")
logger.write_string(", loader_id: ")
logger.write_string("\"")
logger.write_string(self.loader_id)
logger.write_string("\"")
logger.write_string(", timestamp: \{self.timestamp}}")
}
///|
pub impl Show for NetworkResponse with fn output(self, logger) {
logger.write_string("{request_id: \{self.request_id}, url: ")
logger.write_string("\"")
logger.write_string(self.url)
logger.write_string("\"")
logger.write_string(", status: \{self.status}, status_text: ")
logger.write_string("\"")
logger.write_string(self.status_text)
logger.write_string("\"")
logger.write_string(", mime_type: ")
logger.write_string("\"")
logger.write_string(self.mime_type)
logger.write_string("\"")
logger.write_string(", headers: ")
logger.write_object(to_repr(self.headers))
logger.write_string(", body: ")
logger.write_object(to_repr(self.body))
logger.write_string(", ")
logger.write_string(
"content_length: \{self.content_length}, timestamp: \{self.timestamp}, ",
)
logger.write_string("loader_id: ")
logger.write_string("\"")
logger.write_string(self.loader_id)
logger.write_string("\"")
logger.write_string("}")
}
///|
pub impl Show for NetworkEvent with fn output(self, logger) {
match self {
RequestWillBeSent(request) =>
logger.write_string("RequestWillBeSent(\{request})")
ResponseReceived(response) =>
logger.write_string("ResponseReceived(\{response})")
LoadingFinished(request_id~, encoded_data_length~) =>
logger.write_string(
"LoadingFinished(request_id=\{request_id}, encoded_data_length=\{encoded_data_length})",
)
LoadingFailed(request_id~, error~) => {
logger.write_string("LoadingFailed(request_id=\{request_id}, error=")
logger.write_string("\"")
logger.write_string(error)
logger.write_string("\"")
logger.write_string(")")
}
}
}
///|
pub impl Show for FetchResult with fn output(self, logger) {
match self {
Success(response~) => logger.write_string("Success(response=\{response})")
Redirect(location~, status~) => {
logger.write_string("Redirect(location=")
logger.write_string("\"")
logger.write_string(location)
logger.write_string("\"")
logger.write_string(", status=\{status})")
}
Failure(error~) => {
logger.write_string("Failure(error=")
logger.write_string("\"")
logger.write_string(error)
logger.write_string("\"")
logger.write_string(")")
}
}
}
// =============================================================================
// Network Manager
// =============================================================================
///|
/// ネットワークリクエストを管理
pub struct NetworkManager {
/// 進行中のリクエスト
pending_requests : Map[Int, NetworkRequest]
/// 発行されたイベント(外部に通知するため)
mut pending_events : Array[NetworkEvent]
/// Network.enable が呼ばれたか
mut enabled : Bool
}
///|
pub fn NetworkManager::new() -> NetworkManager {
{ pending_requests: {}, pending_events: [], enabled: false }
}
///|
/// Network を有効化
pub fn NetworkManager::enable(self : NetworkManager) -> Unit {
self.enabled = true
}
///|
/// Network を無効化
pub fn NetworkManager::disable(self : NetworkManager) -> Unit {
self.enabled = false
}
///|
/// リクエスト開始を記録
pub fn NetworkManager::start_request(
self : NetworkManager,
request : NetworkRequest,
) -> Unit {
let RequestId(id) = request.id
self.pending_requests[id] = request
if self.enabled {
self.pending_events.push(RequestWillBeSent(request))
}
}
///|
/// レスポンス受信を記録
pub fn NetworkManager::receive_response(
self : NetworkManager,
response : NetworkResponse,
) -> Unit {
if self.enabled {
self.pending_events.push(ResponseReceived(response))
}
}
///|
/// ロード完了を記録
pub fn NetworkManager::finish_loading(
self : NetworkManager,
request_id : RequestId,
encoded_data_length : Int,
) -> Unit {
let RequestId(id) = request_id
self.pending_requests.remove(id)
if self.enabled {
self.pending_events.push(LoadingFinished(request_id~, encoded_data_length~))
}
}
///|
/// ロード失敗を記録
pub fn NetworkManager::fail_loading(
self : NetworkManager,
request_id : RequestId,
error : String,
) -> Unit {
let RequestId(id) = request_id
self.pending_requests.remove(id)
if self.enabled {
self.pending_events.push(LoadingFailed(request_id~, error~))
}
}
///|
/// pending イベントを取得してクリア
pub fn NetworkManager::drain_events(
self : NetworkManager,
) -> Array[NetworkEvent] {
let events = self.pending_events
self.pending_events = []
events
}
///|
/// pending イベントがあるか
pub fn NetworkManager::has_pending_events(self : NetworkManager) -> Bool {
!self.pending_events.is_empty()
}
///|
/// 進行中のリクエスト数
pub fn NetworkManager::pending_count(self : NetworkManager) -> Int {
self.pending_requests.length()
}