Test
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// Video API endpoints for streaming video files via HTTP
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use axum::{
|
||||
body::StreamBody,
|
||||
extract::Path,
|
||||
http::{header, HeaderValue, Response, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
/// Video streaming endpoint handler
|
||||
/// Returns video content with appropriate Content-Type header
|
||||
pub async fn stream_video(
|
||||
Path(video_path): Path<String>,
|
||||
) -> Result<Response<StreamBody<ReaderStream<std::fs::File>>>, StatusCode> {
|
||||
// Validate and sanitize the path to prevent directory traversal
|
||||
let full_path = sanitize_and_resolve_path(&video_path)?;
|
||||
|
||||
// Open the file
|
||||
let file = File::open(&full_path)
|
||||
.map_err(|_| StatusCode::NOT_FOUND)?;
|
||||
|
||||
// Determine content type based on file extension
|
||||
let content_type = get_content_type(&full_path);
|
||||
|
||||
// Create a stream from the file
|
||||
let stream = ReaderStream::new(file);
|
||||
let body = StreamBody::new(stream);
|
||||
|
||||
// Build response with appropriate headers
|
||||
let mut response = Response::builder()
|
||||
.header(header::CONTENT_TYPE, content_type)
|
||||
.header(header::ACCEPT_RANGES, "bytes")
|
||||
.header(
|
||||
header::CONTENT_DISPOSITION,
|
||||
format!("inline; filename="{}"", full_path.file_name().unwrap_or_default().to_string_lossy()),
|
||||
)
|
||||
.body(body)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Sanitize and resolve the video path to prevent directory traversal attacks
|
||||
fn sanitize_and_resolve_path(video_path: &str) -> Result<std::path::PathBuf, StatusCode> {
|
||||
let base_dir = Path::new("/opt/lumière/files"); // Your root folder from .env
|
||||
|
||||
if !base_dir.exists() {
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
let requested_path = Path::new(video_path);
|
||||
|
||||
// Normalize the path and check it's within base_dir
|
||||
let full_path = base_dir.join(requested_path);
|
||||
let full_path = full_path.canonicalize()
|
||||
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||
|
||||
// Security check: ensure the final path is still within base_dir
|
||||
if !full_path.starts_with(base_dir) {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
Ok(full_path)
|
||||
}
|
||||
|
||||
/// Determine Content-Type based on file extension
|
||||
fn get_content_type(path: &Path) -> &'static str {
|
||||
match path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("avi") => "video/x-msvideo",
|
||||
Some("mkv") | Some("mp4") | Some("mov") | Some("webm") => "video/mp4", // Generic video type
|
||||
_ => "application/octet-stream", // Fallback for unknown types
|
||||
}
|
||||
}
|
||||
|
||||
/// Register video routes
|
||||
pub fn register_video_routes(app: axum::Router) -> axum::Router {
|
||||
app.route(
|
||||
"/videos/:video_path*",
|
||||
axum::routing::get(stream_video),
|
||||
)
|
||||
}
|
||||
|
||||
// Example usage in main.rs:
|
||||
/*
|
||||
pub async fn main() {
|
||||
let app = axum::Router::new()
|
||||
.nest("/api/v1", register_video_routes(axum::Router::new()));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1 @@
|
||||
pub mod service;
|
||||
@@ -0,0 +1,73 @@
|
||||
// Video service implementations for streaming files chunk by chunk
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::Path;
|
||||
use std::io::Read;
|
||||
|
||||
/// Basic example: Read file line by line using BufReader
|
||||
pub fn read_file_line_by_line(path: &str) -> std::io::Result<()> {
|
||||
let path = Path::new(path);
|
||||
let file = File::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
// Read line by line
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
println!("Line: {}", line);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read file in fixed-size chunks
|
||||
pub fn read_file_in_chunks(path: &str, chunk_size: usize) -> std::io::Result<()> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut buffer = vec![0; chunk_size];
|
||||
|
||||
loop {
|
||||
let n = file.read(&mut buffer)?;
|
||||
if n == 0 {
|
||||
break; // EOF
|
||||
}
|
||||
println!("Read {} bytes: {:?}", n, &buffer[..n]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Using std::io::copy for streaming between files
|
||||
pub fn copy_file_streaming(input_path: &str, output_path: &str) -> std::io::Result<()> {
|
||||
use std::io;
|
||||
|
||||
let mut input = File::open(input_path)?;
|
||||
let mut output = File::create(output_path)?;
|
||||
|
||||
// Copy in chunks without loading entire file into memory
|
||||
io::copy(&mut input, &mut output)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process large files efficiently with configurable buffer size
|
||||
pub fn process_large_file(path: &str, buffer_size: usize) -> std::io::Result<()> {
|
||||
let file = File::open(path)?;
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = vec![0; buffer_size];
|
||||
|
||||
loop {
|
||||
let bytes_read = reader.read(&mut buffer)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
process_chunk(&buffer[..bytes_read]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Example chunk processing function
|
||||
fn process_chunk(chunk: &[u8]) {
|
||||
// Process your chunk here
|
||||
println!("Processing {} bytes", chunk.len());
|
||||
}
|
||||
Reference in New Issue
Block a user