1use std::net::SocketAddr;
10use std::sync::Arc;
11
12use axum::extract::DefaultBodyLimit;
13use axum::{
14 Router,
15 extract::{Path, State},
16 http::StatusCode,
17 response::{IntoResponse, Json},
18 routing::{get, post},
19};
20use base64::Engine;
21use serde::{Deserialize, Serialize};
22use serde_json::{Value, json};
23use tracing::{error, info, warn};
24
25use crate::instance_handlers::{
26 self, CheckpointRequest as HandlerCheckpointRequest,
27 GetInstanceStatusRequest as HandlerGetStatusRequest, InstanceEvent as HandlerInstanceEvent,
28 InstanceEventType as HandlerEventType, InstanceHandlerState, InstanceStatus,
29 PollSignalsRequest as HandlerPollSignalsRequest,
30 RegisterInstanceRequest as HandlerRegisterRequest,
31 RetryAttemptEvent as HandlerRetryAttemptEvent, SignalAck as HandlerSignalAck, SignalType,
32 SleepRequest as HandlerSleepRequest,
33};
34
35#[derive(Debug, Deserialize)]
41pub struct RegisterRequest {
42 pub tenant_id: String,
44 #[serde(default)]
46 pub checkpoint_id: Option<String>,
47}
48
49#[derive(Debug, Serialize)]
51pub struct RegisterResponse {
52 pub success: bool,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub error: Option<String>,
55}
56
57#[derive(Debug, Deserialize)]
59pub struct CheckpointRequest {
60 pub checkpoint_id: String,
62 pub state: String,
64}
65
66#[derive(Debug, Serialize)]
68pub struct CheckpointResponse {
69 pub found: bool,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub state: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub signal: Option<SignalInfo>,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub custom_signal: Option<CustomSignalInfo>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub last_error: Option<ErrorInfo>,
83}
84
85#[derive(Debug, Serialize)]
87pub struct SignalInfo {
88 pub signal_type: String,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub payload: Option<String>,
91}
92
93#[derive(Debug, Serialize)]
95pub struct CustomSignalInfo {
96 pub checkpoint_id: String,
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub payload: Option<String>,
99}
100
101#[derive(Debug, Serialize)]
103pub struct ErrorInfo {
104 pub code: String,
105 pub message: String,
106}
107
108#[derive(Debug, Serialize)]
110pub struct PollSignalsResponse {
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub signal: Option<SignalInfo>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub custom_signal: Option<CustomSignalInfo>,
115}
116
117#[derive(Debug, Deserialize)]
119pub struct InstanceEventRequest {
120 pub event_type: String,
122 #[serde(default)]
123 pub checkpoint_id: Option<String>,
124 #[serde(default)]
126 pub payload: Option<String>,
127 #[serde(default)]
128 pub subtype: Option<String>,
129}
130
131#[derive(Debug, Deserialize)]
133pub struct SleepRequest {
134 pub duration_ms: u64,
135 pub checkpoint_id: String,
136 pub state: String,
138}
139
140#[derive(Debug, Deserialize)]
142pub struct SignalAckRequest {
143 pub signal_type: String,
144}
145
146#[derive(Debug, Deserialize)]
148pub struct RetryAttemptRequest {
149 pub checkpoint_id: String,
150 pub attempt: u32,
151 #[serde(default)]
152 pub error_message: Option<String>,
153}
154
155#[derive(Debug, Serialize)]
157pub struct SuccessResponse {
158 pub success: bool,
159}
160
161fn signal_type_to_string(st: i32) -> String {
166 match st {
167 0 => "cancel".to_string(), 1 => "pause".to_string(), 2 => "resume".to_string(), _ => format!("unknown({})", st),
171 }
172}
173
174fn event_type_from_string(s: &str) -> i32 {
175 match s {
176 "heartbeat" => HandlerEventType::EventHeartbeat as i32,
177 "completed" => HandlerEventType::EventCompleted as i32,
178 "failed" => HandlerEventType::EventFailed as i32,
179 "suspended" => HandlerEventType::EventSuspended as i32,
180 "custom" => HandlerEventType::EventCustom as i32,
181 _ => HandlerEventType::EventCustom as i32,
182 }
183}
184
185async fn register_handler(
191 State(state): State<Arc<InstanceHandlerState>>,
192 Path(instance_id): Path<String>,
193 Json(body): Json<RegisterRequest>,
194) -> impl IntoResponse {
195 let request = HandlerRegisterRequest {
196 instance_id,
197 tenant_id: body.tenant_id,
198 checkpoint_id: body.checkpoint_id,
199 };
200
201 match instance_handlers::handle_register_instance(&state, request).await {
202 Ok(resp) => {
203 if resp.success {
204 Json(RegisterResponse {
205 success: true,
206 error: None,
207 })
208 .into_response()
209 } else {
210 (
211 StatusCode::BAD_REQUEST,
212 Json(RegisterResponse {
213 success: false,
214 error: Some(resp.error),
215 }),
216 )
217 .into_response()
218 }
219 }
220 Err(e) => {
221 error!("Register handler error: {}", e);
222 (
223 StatusCode::INTERNAL_SERVER_ERROR,
224 Json(json!({
225 "error": e.to_string(),
226 "code": "REGISTER_ERROR"
227 })),
228 )
229 .into_response()
230 }
231 }
232}
233
234async fn checkpoint_handler(
236 State(state): State<Arc<InstanceHandlerState>>,
237 Path(instance_id): Path<String>,
238 Json(body): Json<CheckpointRequest>,
239) -> impl IntoResponse {
240 use base64::Engine;
241
242 let state_bytes = match base64::engine::general_purpose::STANDARD.decode(&body.state) {
243 Ok(b) => b,
244 Err(e) => {
245 return (
246 StatusCode::BAD_REQUEST,
247 Json(json!({
248 "error": format!("Invalid base64 state: {}", e),
249 "code": "INVALID_STATE"
250 })),
251 )
252 .into_response();
253 }
254 };
255
256 let request = HandlerCheckpointRequest {
257 instance_id,
258 checkpoint_id: body.checkpoint_id,
259 state: state_bytes,
260 };
261
262 match instance_handlers::handle_checkpoint(&state, request).await {
263 Ok(resp) => {
264 let signal = resp.pending_signal.map(|s| SignalInfo {
265 signal_type: signal_type_to_string(s.signal_type),
266 payload: if s.payload.is_empty() {
267 None
268 } else {
269 Some(base64::engine::general_purpose::STANDARD.encode(&s.payload))
270 },
271 });
272
273 let custom_signal = resp.custom_signal.map(|cs| CustomSignalInfo {
274 checkpoint_id: cs.checkpoint_id,
275 payload: if cs.payload.is_empty() {
276 None
277 } else {
278 Some(base64::engine::general_purpose::STANDARD.encode(&cs.payload))
279 },
280 });
281
282 let last_error = resp.last_error.map(|e| ErrorInfo {
283 code: e.code,
284 message: e.message,
285 });
286
287 Json(CheckpointResponse {
288 found: resp.found,
289 state: if resp.state.is_empty() {
290 None
291 } else {
292 Some(base64::engine::general_purpose::STANDARD.encode(&resp.state))
293 },
294 signal,
295 custom_signal,
296 last_error,
297 })
298 .into_response()
299 }
300 Err(e) => {
301 error!("Checkpoint handler error: {}", e);
302 (
303 StatusCode::INTERNAL_SERVER_ERROR,
304 Json(json!({
305 "error": e.to_string(),
306 "code": "CHECKPOINT_ERROR"
307 })),
308 )
309 .into_response()
310 }
311 }
312}
313
314async fn poll_signals_handler(
316 State(state): State<Arc<InstanceHandlerState>>,
317 Path(instance_id): Path<String>,
318) -> impl IntoResponse {
319 let request = HandlerPollSignalsRequest {
320 instance_id,
321 checkpoint_id: None,
322 };
323
324 match instance_handlers::handle_poll_signals(&state, request).await {
325 Ok(resp) => {
326 let signal = resp.signal.map(|s| SignalInfo {
327 signal_type: signal_type_to_string(s.signal_type),
328 payload: if s.payload.is_empty() {
329 None
330 } else {
331 Some(base64::engine::general_purpose::STANDARD.encode(&s.payload))
332 },
333 });
334
335 let custom_signal = resp.custom_signal.map(|cs| CustomSignalInfo {
336 checkpoint_id: cs.checkpoint_id,
337 payload: if cs.payload.is_empty() {
338 None
339 } else {
340 Some(base64::engine::general_purpose::STANDARD.encode(&cs.payload))
341 },
342 });
343
344 Json(PollSignalsResponse {
345 signal,
346 custom_signal,
347 })
348 .into_response()
349 }
350 Err(e) => {
351 error!("Poll signals error: {}", e);
352 (
353 StatusCode::INTERNAL_SERVER_ERROR,
354 Json(json!({
355 "error": e.to_string(),
356 "code": "POLL_SIGNALS_ERROR"
357 })),
358 )
359 .into_response()
360 }
361 }
362}
363
364async fn poll_custom_signal_handler(
366 State(state): State<Arc<InstanceHandlerState>>,
367 Path((instance_id, signal_id)): Path<(String, String)>,
368) -> impl IntoResponse {
369 let request = HandlerPollSignalsRequest {
370 instance_id,
371 checkpoint_id: Some(signal_id),
372 };
373
374 match instance_handlers::handle_poll_signals(&state, request).await {
375 Ok(resp) => {
376 let custom_signal = resp.custom_signal.map(|cs| CustomSignalInfo {
377 checkpoint_id: cs.checkpoint_id,
378 payload: if cs.payload.is_empty() {
379 None
380 } else {
381 Some(base64::engine::general_purpose::STANDARD.encode(&cs.payload))
382 },
383 });
384
385 Json(PollSignalsResponse {
386 signal: None,
387 custom_signal,
388 })
389 .into_response()
390 }
391 Err(e) => {
392 error!("Poll custom signal error: {}", e);
393 (
394 StatusCode::INTERNAL_SERVER_ERROR,
395 Json(json!({
396 "error": e.to_string(),
397 "code": "POLL_CUSTOM_SIGNAL_ERROR"
398 })),
399 )
400 .into_response()
401 }
402 }
403}
404
405async fn instance_event_handler(
407 State(state): State<Arc<InstanceHandlerState>>,
408 Path(instance_id): Path<String>,
409 Json(body): Json<InstanceEventRequest>,
410) -> impl IntoResponse {
411 let payload = body
412 .payload
413 .as_deref()
414 .map(|p| base64::engine::general_purpose::STANDARD.decode(p))
415 .transpose();
416
417 let payload = match payload {
418 Ok(p) => p.unwrap_or_default(),
419 Err(e) => {
420 return (
421 StatusCode::BAD_REQUEST,
422 Json(json!({
423 "error": format!("Invalid base64 payload: {}", e),
424 "code": "INVALID_PAYLOAD"
425 })),
426 )
427 .into_response();
428 }
429 };
430
431 let event = HandlerInstanceEvent {
432 instance_id,
433 event_type: event_type_from_string(&body.event_type),
434 checkpoint_id: body.checkpoint_id,
435 payload,
436 timestamp_ms: chrono::Utc::now().timestamp_millis(),
437 subtype: body.subtype,
438 };
439
440 match instance_handlers::handle_instance_event(&state, event).await {
441 Ok(resp) => {
442 if resp.success {
443 Json(SuccessResponse { success: true }).into_response()
444 } else {
445 let error = resp.error.unwrap_or_else(|| "Unknown error".to_string());
446 (
447 StatusCode::INTERNAL_SERVER_ERROR,
448 Json(json!({
449 "success": false,
450 "error": error,
451 })),
452 )
453 .into_response()
454 }
455 }
456 Err(e) => {
457 error!("Instance event error: {}", e);
458 (
459 StatusCode::INTERNAL_SERVER_ERROR,
460 Json(json!({
461 "error": e.to_string(),
462 "code": "EVENT_ERROR"
463 })),
464 )
465 .into_response()
466 }
467 }
468}
469
470async fn completed_handler(
472 State(state): State<Arc<InstanceHandlerState>>,
473 Path(instance_id): Path<String>,
474 Json(body): Json<Value>,
475) -> impl IntoResponse {
476 let payload = body
477 .get("output")
478 .and_then(|v| v.as_str())
479 .map(|s| base64::engine::general_purpose::STANDARD.decode(s))
480 .transpose();
481
482 let payload = match payload {
483 Ok(p) => p.unwrap_or_default(),
484 Err(e) => {
485 return (
486 StatusCode::BAD_REQUEST,
487 Json(json!({
488 "error": format!("Invalid base64 output: {}", e),
489 "code": "INVALID_OUTPUT"
490 })),
491 )
492 .into_response();
493 }
494 };
495
496 let event = HandlerInstanceEvent {
497 instance_id,
498 event_type: HandlerEventType::EventCompleted as i32,
499 checkpoint_id: None,
500 payload,
501 timestamp_ms: chrono::Utc::now().timestamp_millis(),
502 subtype: None,
503 };
504
505 match instance_handlers::handle_instance_event(&state, event).await {
506 Ok(_) => Json(SuccessResponse { success: true }).into_response(),
507 Err(e) => {
508 error!("Completed handler error: {}", e);
509 (
510 StatusCode::INTERNAL_SERVER_ERROR,
511 Json(json!({
512 "error": e.to_string(),
513 "code": "COMPLETED_ERROR"
514 })),
515 )
516 .into_response()
517 }
518 }
519}
520
521async fn failed_handler(
523 State(state): State<Arc<InstanceHandlerState>>,
524 Path(instance_id): Path<String>,
525 Json(body): Json<Value>,
526) -> impl IntoResponse {
527 let error_msg = body
528 .get("error")
529 .and_then(|v| v.as_str())
530 .unwrap_or("Unknown error");
531
532 let event = HandlerInstanceEvent {
533 instance_id,
534 event_type: HandlerEventType::EventFailed as i32,
535 checkpoint_id: None,
536 payload: error_msg.as_bytes().to_vec(),
537 timestamp_ms: chrono::Utc::now().timestamp_millis(),
538 subtype: None,
539 };
540
541 match instance_handlers::handle_instance_event(&state, event).await {
542 Ok(_) => Json(SuccessResponse { success: true }).into_response(),
543 Err(e) => {
544 error!("Failed handler error: {}", e);
545 (
546 StatusCode::INTERNAL_SERVER_ERROR,
547 Json(json!({
548 "error": e.to_string(),
549 "code": "FAILED_ERROR"
550 })),
551 )
552 .into_response()
553 }
554 }
555}
556
557async fn suspended_handler(
559 State(state): State<Arc<InstanceHandlerState>>,
560 Path(instance_id): Path<String>,
561) -> impl IntoResponse {
562 let event = HandlerInstanceEvent {
563 instance_id,
564 event_type: HandlerEventType::EventSuspended as i32,
565 checkpoint_id: None,
566 payload: Vec::new(),
567 timestamp_ms: chrono::Utc::now().timestamp_millis(),
568 subtype: None,
569 };
570
571 match instance_handlers::handle_instance_event(&state, event).await {
572 Ok(_) => Json(SuccessResponse { success: true }).into_response(),
573 Err(e) => {
574 error!("Suspended handler error: {}", e);
575 (
576 StatusCode::INTERNAL_SERVER_ERROR,
577 Json(json!({
578 "error": e.to_string(),
579 "code": "SUSPENDED_ERROR"
580 })),
581 )
582 .into_response()
583 }
584 }
585}
586
587async fn sleep_handler(
589 State(state): State<Arc<InstanceHandlerState>>,
590 Path(instance_id): Path<String>,
591 Json(body): Json<SleepRequest>,
592) -> impl IntoResponse {
593 let state_bytes = match base64::engine::general_purpose::STANDARD.decode(&body.state) {
594 Ok(b) => b,
595 Err(e) => {
596 return (
597 StatusCode::BAD_REQUEST,
598 Json(json!({
599 "error": format!("Invalid base64 state: {}", e),
600 "code": "INVALID_STATE"
601 })),
602 )
603 .into_response();
604 }
605 };
606
607 let request = HandlerSleepRequest {
608 instance_id,
609 duration_ms: body.duration_ms,
610 checkpoint_id: body.checkpoint_id,
611 state: state_bytes,
612 };
613
614 match instance_handlers::handle_sleep(&state, request).await {
615 Ok(_) => Json(SuccessResponse { success: true }).into_response(),
616 Err(e) => {
617 error!("Sleep handler error: {}", e);
618 (
619 StatusCode::INTERNAL_SERVER_ERROR,
620 Json(json!({
621 "error": e.to_string(),
622 "code": "SLEEP_ERROR"
623 })),
624 )
625 .into_response()
626 }
627 }
628}
629
630async fn signal_ack_handler(
632 State(state): State<Arc<InstanceHandlerState>>,
633 Path(instance_id): Path<String>,
634 Json(body): Json<SignalAckRequest>,
635) -> impl IntoResponse {
636 let signal_type = match body.signal_type.as_str() {
637 "cancel" => SignalType::SignalCancel as i32,
638 "pause" => SignalType::SignalPause as i32,
639 "resume" => SignalType::SignalResume as i32,
640 _ => {
641 return (
642 StatusCode::BAD_REQUEST,
643 Json(json!({
644 "error": format!("Unknown signal type: {}", body.signal_type),
645 "code": "INVALID_SIGNAL_TYPE"
646 })),
647 )
648 .into_response();
649 }
650 };
651
652 let ack = HandlerSignalAck {
653 instance_id,
654 signal_type,
655 acknowledged: true,
656 };
657
658 match instance_handlers::handle_signal_ack(&state, ack).await {
659 Ok(()) => Json(SuccessResponse { success: true }).into_response(),
660 Err(e) => {
661 warn!("Signal ack error: {}", e);
662 (
663 StatusCode::INTERNAL_SERVER_ERROR,
664 Json(json!({
665 "error": e.to_string(),
666 "code": "SIGNAL_ACK_ERROR"
667 })),
668 )
669 .into_response()
670 }
671 }
672}
673
674async fn retry_handler(
676 State(state): State<Arc<InstanceHandlerState>>,
677 Path(instance_id): Path<String>,
678 Json(body): Json<RetryAttemptRequest>,
679) -> impl IntoResponse {
680 let event = HandlerRetryAttemptEvent {
681 instance_id,
682 checkpoint_id: body.checkpoint_id,
683 attempt_number: body.attempt,
684 error_message: body.error_message,
685 error_metadata: None,
686 timestamp_ms: chrono::Utc::now().timestamp_millis(),
687 };
688
689 match instance_handlers::handle_retry_attempt(&state, event).await {
690 Ok(()) => Json(SuccessResponse { success: true }).into_response(),
691 Err(e) => {
692 warn!("Retry attempt error: {}", e);
693 (
694 StatusCode::INTERNAL_SERVER_ERROR,
695 Json(json!({
696 "error": e.to_string(),
697 "code": "RETRY_ERROR"
698 })),
699 )
700 .into_response()
701 }
702 }
703}
704
705#[derive(Debug, Serialize)]
707pub struct InstanceStatusResponse {
708 pub found: bool,
709 pub instance_id: String,
710 #[serde(skip_serializing_if = "Option::is_none")]
711 pub status: Option<String>,
712 #[serde(skip_serializing_if = "Option::is_none")]
713 pub checkpoint_id: Option<String>,
714 #[serde(skip_serializing_if = "Option::is_none")]
715 pub output: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
717 pub error: Option<String>,
718}
719
720async fn status_handler(
722 State(state): State<Arc<InstanceHandlerState>>,
723 Path(instance_id): Path<String>,
724) -> impl IntoResponse {
725 let request = HandlerGetStatusRequest {
726 instance_id: instance_id.clone(),
727 };
728
729 match instance_handlers::handle_get_instance_status(&state, request).await {
730 Ok(resp) => {
731 let status_str = match InstanceStatus::try_from_i32(resp.status) {
732 Some(InstanceStatus::StatusPending) => "pending",
733 Some(InstanceStatus::StatusRunning) => "running",
734 Some(InstanceStatus::StatusSuspended) => "suspended",
735 Some(InstanceStatus::StatusCompleted) => "completed",
736 Some(InstanceStatus::StatusFailed) => "failed",
737 Some(InstanceStatus::StatusCancelled) => "cancelled",
738 _ => "unknown",
739 };
740
741 let output = resp
742 .output
743 .as_ref()
744 .map(|o| base64::engine::general_purpose::STANDARD.encode(o));
745
746 let found =
747 status_str != "unknown" || resp.error.as_deref() != Some("Instance not found");
748
749 Json(InstanceStatusResponse {
750 found,
751 instance_id: resp.instance_id,
752 status: Some(status_str.to_string()),
753 checkpoint_id: resp.checkpoint_id,
754 output,
755 error: resp.error,
756 })
757 .into_response()
758 }
759 Err(e) => {
760 error!("Status handler error: {}", e);
761 (
762 StatusCode::INTERNAL_SERVER_ERROR,
763 Json(json!({
764 "error": e.to_string(),
765 "code": "STATUS_ERROR"
766 })),
767 )
768 .into_response()
769 }
770 }
771}
772
773async fn input_handler(
775 State(state): State<Arc<InstanceHandlerState>>,
776 Path(instance_id): Path<String>,
777) -> impl IntoResponse {
778 match state.persistence.get_instance(&instance_id).await {
779 Ok(Some(inst)) => {
780 if let Some(input_bytes) = inst.input {
781 let encoded = base64::engine::general_purpose::STANDARD.encode(&input_bytes);
782 Json(json!({
783 "found": true,
784 "instance_id": instance_id,
785 "input": encoded,
786 }))
787 .into_response()
788 } else {
789 Json(json!({
790 "found": true,
791 "instance_id": instance_id,
792 "input": null,
793 }))
794 .into_response()
795 }
796 }
797 Ok(None) => (
798 StatusCode::NOT_FOUND,
799 Json(json!({
800 "found": false,
801 "instance_id": instance_id,
802 "error": "Instance not found",
803 })),
804 )
805 .into_response(),
806 Err(e) => {
807 error!("Input handler error: {}", e);
808 (
809 StatusCode::INTERNAL_SERVER_ERROR,
810 Json(json!({
811 "error": e.to_string(),
812 "code": "INPUT_ERROR"
813 })),
814 )
815 .into_response()
816 }
817 }
818}
819
820async fn health_handler(State(state): State<Arc<InstanceHandlerState>>) -> impl IntoResponse {
822 let db_ok = state.persistence.health_check_db().await.unwrap_or(false);
823 if db_ok {
824 Json(json!({"status": "healthy"})).into_response()
825 } else {
826 (
827 StatusCode::SERVICE_UNAVAILABLE,
828 Json(json!({
829 "status": "unhealthy",
830 "error": "database check failed"
831 })),
832 )
833 .into_response()
834 }
835}
836
837pub fn instance_http_router(state: Arc<InstanceHandlerState>) -> Router {
845 Router::new()
846 .route(
848 "/api/v1/instances/{instance_id}/register",
849 post(register_handler),
850 )
851 .route(
853 "/api/v1/instances/{instance_id}/checkpoint",
854 post(checkpoint_handler),
855 )
856 .route(
858 "/api/v1/instances/{instance_id}/signals",
859 get(poll_signals_handler),
860 )
861 .route(
862 "/api/v1/instances/{instance_id}/signals/{signal_id}",
863 get(poll_custom_signal_handler),
864 )
865 .route(
866 "/api/v1/instances/{instance_id}/signals/ack",
867 post(signal_ack_handler),
868 )
869 .route(
871 "/api/v1/instances/{instance_id}/completed",
872 post(completed_handler),
873 )
874 .route(
875 "/api/v1/instances/{instance_id}/failed",
876 post(failed_handler),
877 )
878 .route(
879 "/api/v1/instances/{instance_id}/suspended",
880 post(suspended_handler),
881 )
882 .route(
883 "/api/v1/instances/{instance_id}/events",
884 post(instance_event_handler),
885 )
886 .route("/api/v1/instances/{instance_id}/sleep", post(sleep_handler))
888 .route("/api/v1/instances/{instance_id}/retry", post(retry_handler))
890 .route(
892 "/api/v1/instances/{instance_id}/status",
893 get(status_handler),
894 )
895 .route("/api/v1/instances/{instance_id}/input", get(input_handler))
897 .route("/health", get(health_handler))
899 .layer(DefaultBodyLimit::max(64 * 1024 * 1024))
900 .with_state(state)
901}
902
903pub async fn run_http_server(
908 bind_addr: SocketAddr,
909 state: Arc<InstanceHandlerState>,
910) -> anyhow::Result<()> {
911 let app = instance_http_router(state);
912 let listener = tokio::net::TcpListener::bind(bind_addr).await?;
913
914 info!(addr = %bind_addr, "Instance HTTP server starting");
915
916 axum::serve(listener, app)
917 .await
918 .map_err(|e| anyhow::anyhow!("HTTP server error: {}", e))?;
919
920 info!("Instance HTTP server stopped");
921 Ok(())
922}