diff --git a/src/index.js b/src/index.js index 5e4a1a5..8dec3e1 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ import { CONFIG, createConfig } from './config/index.js'; import { SORTED_PLATFORMS, transformPath } from './config/platforms.js'; import { configureAIHeaders, isAIInferenceRequest } from './protocols/ai.js'; +import { configureHuggingFaceHeaders, isHuggingFaceAPIRequest } from './protocols/huggingface.js'; import { fetchToken, getScopeFromUrl, @@ -126,7 +127,10 @@ async function handleRequest(request, env, ctx) { // Check if this is an AI inference request const isAI = isAIInferenceRequest(request, url); - // Check cache first (skip cache for Git, Git LFS, Docker, and AI inference operations) + // Check if this is a Hugging Face API request + const isHF = isHuggingFaceAPIRequest(request, url); + + // Check cache first (skip cache for Git, Git LFS, Docker, AI inference, and HF API operations) /** @type {Cache | null} */ // @ts-ignore - Cloudflare Workers cache API const cache = @@ -134,7 +138,7 @@ async function handleRequest(request, env, ctx) { ? /** @type {any} */ (caches).default // eslint-disable-line jsdoc/reject-any-type : null; - if (cache && !isGit && !isGitLFS && !isDocker && !isAI) { + if (cache && !isGit && !isGitLFS && !isDocker && !isAI && !isHF) { try { // For Range requests, try cache match first const cacheKey = new Request(targetUrl, { @@ -177,10 +181,10 @@ async function handleRequest(request, env, ctx) { redirect: 'follow' }; - // Add body for POST/PUT/PATCH requests (Git/Docker/AI inference operations) + // Add body for POST/PUT/PATCH/DELETE requests (Git/Docker/AI/HF operations) if ( - ['POST', 'PUT', 'PATCH'].includes(request.method) && - (isGit || isGitLFS || isDocker || isAI) + ['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method) && + (isGit || isGitLFS || isDocker || isAI || isHF) ) { fetchOptions.body = request.body; } @@ -188,9 +192,9 @@ async function handleRequest(request, env, ctx) { // Cast headers to Headers for proper typing const requestHeaders = /** @type {Headers} */ (fetchOptions.headers); - // Set appropriate headers for Git/Docker/AI vs regular requests - if (isGit || isGitLFS || isDocker || isAI) { - // For Git/Docker/AI operations, copy all headers from the original request + // Set appropriate headers for Git/Docker/AI/HF vs regular requests + if (isGit || isGitLFS || isDocker || isAI || isHF) { + // For Git/Docker/AI/HF operations, copy all headers from the original request // This ensures protocol compliance for (const [key, value] of request.headers.entries()) { // Skip headers that might cause issues with proxying @@ -209,6 +213,10 @@ async function handleRequest(request, env, ctx) { if (isAI) { configureAIHeaders(requestHeaders, request); } + + if (isHF) { + configureHuggingFaceHeaders(requestHeaders, request); + } } else { // Regular file download headers Object.assign(fetchOptions, { @@ -617,8 +625,9 @@ async function handleRequest(request, env, ctx) { const isDocker = isDockerRequest(request, new URL(request.url)); const isAI = isAIInferenceRequest(request, new URL(request.url)); const isGitLFS = isGitLFSRequest(request, new URL(request.url)); + const isHF = isHuggingFaceAPIRequest(request, new URL(request.url)); - return isGit || isGitLFS || isDocker || isAI + return isGit || isGitLFS || isDocker || isAI || isHF ? response : addPerformanceHeaders(response, monitor); } diff --git a/src/protocols/huggingface.js b/src/protocols/huggingface.js new file mode 100644 index 0000000..078fbf1 --- /dev/null +++ b/src/protocols/huggingface.js @@ -0,0 +1,63 @@ +/** + * Xget - High-performance acceleration engine for developer resources + * Copyright (C) 2025 Xi Xu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Hugging Face protocol handler for Xget + */ + +/** + * Detects if a request is a Hugging Face API operation. + * + * Identifies Hugging Face API requests by checking for: + * - Hugging Face platform prefix (/hf/) + * - API path segment (/api/) + * + * @param {Request} request - The incoming request object + * @param {URL} url - Parsed URL object + * @returns {boolean} True if this is a Hugging Face API operation + */ +export function isHuggingFaceAPIRequest(request, url) { + // Check for Hugging Face API endpoints + if (url.pathname.startsWith('/hf/api/')) { + return true; + } + + // Also check for token endpoint which is often used + if (url.pathname.startsWith('/hf/token')) { + return true; + } + + return false; +} + +/** + * Configures headers for Hugging Face API requests. + * + * @param {Headers} headers - The headers object to modify + * @param {Request} request - The original request + */ +export function configureHuggingFaceHeaders(headers, request) { + // Pass through Authorization header if present + if (request.headers.has('Authorization')) { + headers.set('Authorization', request.headers.get('Authorization')); + } + + if (request.method === 'POST' && !headers.has('Content-Type')) { + headers.set('Content-Type', 'application/json'); + } +} diff --git a/src/utils/validation.js b/src/utils/validation.js index 13745ab..3932721 100644 --- a/src/utils/validation.js +++ b/src/utils/validation.js @@ -25,6 +25,7 @@ import { CONFIG } from '../config/index.js'; // Imported protocol checks import { isAIInferenceRequest } from '../protocols/ai.js'; import { isGitLFSRequest, isGitRequest } from '../protocols/git.js'; +import { isHuggingFaceAPIRequest } from '../protocols/huggingface.js'; /** * Detects if a request is a container registry operation (Docker/OCI). @@ -72,7 +73,7 @@ export function isDockerRequest(request, url) { } // Re-export for standard usage -export { isAIInferenceRequest, isGitLFSRequest, isGitRequest }; +export { isAIInferenceRequest, isGitLFSRequest, isGitRequest, isHuggingFaceAPIRequest }; /** * Validates incoming requests against security rules. @@ -90,15 +91,16 @@ export { isAIInferenceRequest, isGitLFSRequest, isGitRequest }; * @returns {{valid: boolean, error?: string, status?: number}} Validation result object */ export function validateRequest(request, url, config = CONFIG) { - // Allow POST method for Git, Git LFS, Docker, and AI inference operations + // Allow POST method for Git, Git LFS, Docker, AI inference, and HF API operations const isGit = isGitRequest(request, url); const isGitLFS = isGitLFSRequest(request, url); const isDocker = isDockerRequest(request, url); const isAI = isAIInferenceRequest(request, url); + const isHF = isHuggingFaceAPIRequest(request, url); const allowedMethods = - isGit || isGitLFS || isDocker || isAI - ? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH'] + isGit || isGitLFS || isDocker || isAI || isHF + ? ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'] : config.SECURITY.ALLOWED_METHODS; if (!allowedMethods.includes(request.method)) {