mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-09 21:04:24 +00:00
staticweb package for easier static web server hosting
This commit is contained in:
103
pkg/server/staticweb/policies/cache.go
Normal file
103
pkg/server/staticweb/policies/cache.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SimpleCachePolicy implements a basic cache policy with a single TTL for all files.
|
||||
type SimpleCachePolicy struct {
|
||||
cacheTime int // Cache duration in seconds
|
||||
}
|
||||
|
||||
// NewSimpleCachePolicy creates a new SimpleCachePolicy with the given cache time in seconds.
|
||||
func NewSimpleCachePolicy(cacheTimeSeconds int) *SimpleCachePolicy {
|
||||
return &SimpleCachePolicy{
|
||||
cacheTime: cacheTimeSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCacheTime returns the cache duration for any file.
|
||||
func (p *SimpleCachePolicy) GetCacheTime(filePath string) int {
|
||||
return p.cacheTime
|
||||
}
|
||||
|
||||
// GetCacheHeaders returns the Cache-Control header for the given file.
|
||||
func (p *SimpleCachePolicy) GetCacheHeaders(filePath string) map[string]string {
|
||||
if p.cacheTime <= 0 {
|
||||
return map[string]string{
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"Cache-Control": fmt.Sprintf("public, max-age=%d", p.cacheTime),
|
||||
}
|
||||
}
|
||||
|
||||
// ExtensionBasedCachePolicy implements a cache policy that varies by file extension.
|
||||
type ExtensionBasedCachePolicy struct {
|
||||
rules map[string]int // Extension -> cache time in seconds
|
||||
defaultTime int // Default cache time for unmatched extensions
|
||||
}
|
||||
|
||||
// NewExtensionBasedCachePolicy creates a new ExtensionBasedCachePolicy.
|
||||
// rules maps file extensions (with leading dot, e.g., ".js") to cache times in seconds.
|
||||
// defaultTime is used for files that don't match any rule.
|
||||
func NewExtensionBasedCachePolicy(rules map[string]int, defaultTime int) *ExtensionBasedCachePolicy {
|
||||
return &ExtensionBasedCachePolicy{
|
||||
rules: rules,
|
||||
defaultTime: defaultTime,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCacheTime returns the cache duration based on the file extension.
|
||||
func (p *ExtensionBasedCachePolicy) GetCacheTime(filePath string) int {
|
||||
ext := strings.ToLower(path.Ext(filePath))
|
||||
if cacheTime, ok := p.rules[ext]; ok {
|
||||
return cacheTime
|
||||
}
|
||||
return p.defaultTime
|
||||
}
|
||||
|
||||
// GetCacheHeaders returns cache headers based on the file extension.
|
||||
func (p *ExtensionBasedCachePolicy) GetCacheHeaders(filePath string) map[string]string {
|
||||
cacheTime := p.GetCacheTime(filePath)
|
||||
|
||||
if cacheTime <= 0 {
|
||||
return map[string]string{
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"Cache-Control": fmt.Sprintf("public, max-age=%d", cacheTime),
|
||||
}
|
||||
}
|
||||
|
||||
// NoCachePolicy implements a cache policy that disables all caching.
|
||||
type NoCachePolicy struct{}
|
||||
|
||||
// NewNoCachePolicy creates a new NoCachePolicy.
|
||||
func NewNoCachePolicy() *NoCachePolicy {
|
||||
return &NoCachePolicy{}
|
||||
}
|
||||
|
||||
// GetCacheTime always returns 0 (no caching).
|
||||
func (p *NoCachePolicy) GetCacheTime(filePath string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetCacheHeaders returns headers that disable caching.
|
||||
func (p *NoCachePolicy) GetCacheHeaders(filePath string) map[string]string {
|
||||
return map[string]string{
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
}
|
||||
}
|
||||
159
pkg/server/staticweb/policies/fallback.go
Normal file
159
pkg/server/staticweb/policies/fallback.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NoFallback implements a fallback strategy that never falls back.
|
||||
// All requests for missing files will result in 404 responses.
|
||||
type NoFallback struct{}
|
||||
|
||||
// NewNoFallback creates a new NoFallback strategy.
|
||||
func NewNoFallback() *NoFallback {
|
||||
return &NoFallback{}
|
||||
}
|
||||
|
||||
// ShouldFallback always returns false.
|
||||
func (f *NoFallback) ShouldFallback(filePath string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetFallbackPath returns an empty string (never called since ShouldFallback returns false).
|
||||
func (f *NoFallback) GetFallbackPath(filePath string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// HTMLFallbackStrategy implements a fallback strategy for Single Page Applications (SPAs).
|
||||
// It serves a specified HTML file (typically index.html) for non-file requests.
|
||||
type HTMLFallbackStrategy struct {
|
||||
indexFile string
|
||||
}
|
||||
|
||||
// NewHTMLFallbackStrategy creates a new HTMLFallbackStrategy.
|
||||
// indexFile is the path to the HTML file to serve (e.g., "index.html", "/index.html").
|
||||
func NewHTMLFallbackStrategy(indexFile string) *HTMLFallbackStrategy {
|
||||
return &HTMLFallbackStrategy{
|
||||
indexFile: indexFile,
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldFallback returns true for requests that don't look like static assets.
|
||||
func (f *HTMLFallbackStrategy) ShouldFallback(filePath string) bool {
|
||||
// Always fall back unless it looks like a static asset
|
||||
return !f.isStaticAsset(filePath)
|
||||
}
|
||||
|
||||
// GetFallbackPath returns the index file path.
|
||||
func (f *HTMLFallbackStrategy) GetFallbackPath(filePath string) string {
|
||||
return f.indexFile
|
||||
}
|
||||
|
||||
// isStaticAsset checks if the path looks like a static asset (has a file extension).
|
||||
func (f *HTMLFallbackStrategy) isStaticAsset(filePath string) bool {
|
||||
return path.Ext(filePath) != ""
|
||||
}
|
||||
|
||||
// ExtensionBasedFallback implements a fallback strategy that skips fallback for known static file extensions.
|
||||
// This is the behavior from the original StaticHTMLFallbackHandler.
|
||||
type ExtensionBasedFallback struct {
|
||||
staticExtensions map[string]bool
|
||||
fallbackPath string
|
||||
}
|
||||
|
||||
// NewExtensionBasedFallback creates a new ExtensionBasedFallback strategy.
|
||||
// staticExtensions is a list of file extensions (with leading dot) that should NOT use fallback.
|
||||
// fallbackPath is the file to serve when fallback is triggered.
|
||||
func NewExtensionBasedFallback(staticExtensions []string, fallbackPath string) *ExtensionBasedFallback {
|
||||
extMap := make(map[string]bool)
|
||||
for _, ext := range staticExtensions {
|
||||
if !strings.HasPrefix(ext, ".") {
|
||||
ext = "." + ext
|
||||
}
|
||||
extMap[strings.ToLower(ext)] = true
|
||||
}
|
||||
|
||||
return &ExtensionBasedFallback{
|
||||
staticExtensions: extMap,
|
||||
fallbackPath: fallbackPath,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultExtensionBasedFallback creates an ExtensionBasedFallback with common web asset extensions.
|
||||
// This matches the behavior of the original StaticHTMLFallbackHandler.
|
||||
func NewDefaultExtensionBasedFallback(fallbackPath string) *ExtensionBasedFallback {
|
||||
return NewExtensionBasedFallback([]string{
|
||||
".js", ".css", ".png", ".svg", ".ico", ".json",
|
||||
".jpg", ".jpeg", ".gif", ".woff", ".woff2", ".ttf", ".eot",
|
||||
}, fallbackPath)
|
||||
}
|
||||
|
||||
// ShouldFallback returns true if the file path doesn't have a static asset extension.
|
||||
func (f *ExtensionBasedFallback) ShouldFallback(filePath string) bool {
|
||||
ext := strings.ToLower(path.Ext(filePath))
|
||||
|
||||
// If it's a known static extension, don't fallback
|
||||
if f.staticExtensions[ext] {
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, try fallback
|
||||
return true
|
||||
}
|
||||
|
||||
// GetFallbackPath returns the configured fallback path.
|
||||
func (f *ExtensionBasedFallback) GetFallbackPath(filePath string) string {
|
||||
return f.fallbackPath
|
||||
}
|
||||
|
||||
// HTMLExtensionFallback implements a fallback strategy that appends .html to paths.
|
||||
// This tries to serve {path}.html for missing files.
|
||||
type HTMLExtensionFallback struct {
|
||||
staticExtensions map[string]bool
|
||||
}
|
||||
|
||||
// NewHTMLExtensionFallback creates a new HTMLExtensionFallback strategy.
|
||||
func NewHTMLExtensionFallback(staticExtensions []string) *HTMLExtensionFallback {
|
||||
extMap := make(map[string]bool)
|
||||
for _, ext := range staticExtensions {
|
||||
if !strings.HasPrefix(ext, ".") {
|
||||
ext = "." + ext
|
||||
}
|
||||
extMap[strings.ToLower(ext)] = true
|
||||
}
|
||||
|
||||
return &HTMLExtensionFallback{
|
||||
staticExtensions: extMap,
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldFallback returns true if the path doesn't have a static extension or .html.
|
||||
func (f *HTMLExtensionFallback) ShouldFallback(filePath string) bool {
|
||||
ext := strings.ToLower(path.Ext(filePath))
|
||||
|
||||
// If it's a known static extension, don't fallback
|
||||
if f.staticExtensions[ext] {
|
||||
return false
|
||||
}
|
||||
|
||||
// If it already has .html, don't fallback
|
||||
if ext == ".html" || ext == ".htm" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetFallbackPath returns the path with .html appended.
|
||||
func (f *HTMLExtensionFallback) GetFallbackPath(filePath string) string {
|
||||
cleanPath := path.Clean(filePath)
|
||||
if !strings.HasSuffix(filePath, "/") {
|
||||
cleanPath = strings.TrimRight(cleanPath, "/")
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(strings.ToLower(cleanPath), ".html") {
|
||||
return cleanPath + ".html"
|
||||
}
|
||||
|
||||
return cleanPath
|
||||
}
|
||||
245
pkg/server/staticweb/policies/mime.go
Normal file
245
pkg/server/staticweb/policies/mime.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package policies
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DefaultMIMEResolver implements a MIME type resolver using Go's standard mime package
|
||||
// and a set of common web file type mappings.
|
||||
type DefaultMIMEResolver struct {
|
||||
customTypes map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewDefaultMIMEResolver creates a new DefaultMIMEResolver with common web MIME types.
|
||||
func NewDefaultMIMEResolver() *DefaultMIMEResolver {
|
||||
resolver := &DefaultMIMEResolver{
|
||||
customTypes: make(map[string]string),
|
||||
}
|
||||
|
||||
// JavaScript & TypeScript
|
||||
resolver.RegisterMIMEType(".js", "application/javascript")
|
||||
resolver.RegisterMIMEType(".mjs", "application/javascript")
|
||||
resolver.RegisterMIMEType(".cjs", "application/javascript")
|
||||
resolver.RegisterMIMEType(".ts", "text/typescript")
|
||||
resolver.RegisterMIMEType(".tsx", "text/tsx")
|
||||
resolver.RegisterMIMEType(".jsx", "text/jsx")
|
||||
|
||||
// CSS & Styling
|
||||
resolver.RegisterMIMEType(".css", "text/css")
|
||||
resolver.RegisterMIMEType(".scss", "text/x-scss")
|
||||
resolver.RegisterMIMEType(".sass", "text/x-sass")
|
||||
resolver.RegisterMIMEType(".less", "text/x-less")
|
||||
|
||||
// HTML & XML
|
||||
resolver.RegisterMIMEType(".html", "text/html")
|
||||
resolver.RegisterMIMEType(".htm", "text/html")
|
||||
resolver.RegisterMIMEType(".xml", "application/xml")
|
||||
resolver.RegisterMIMEType(".xhtml", "application/xhtml+xml")
|
||||
|
||||
// Images - Raster
|
||||
resolver.RegisterMIMEType(".png", "image/png")
|
||||
resolver.RegisterMIMEType(".jpg", "image/jpeg")
|
||||
resolver.RegisterMIMEType(".jpeg", "image/jpeg")
|
||||
resolver.RegisterMIMEType(".gif", "image/gif")
|
||||
resolver.RegisterMIMEType(".webp", "image/webp")
|
||||
resolver.RegisterMIMEType(".avif", "image/avif")
|
||||
resolver.RegisterMIMEType(".bmp", "image/bmp")
|
||||
resolver.RegisterMIMEType(".tiff", "image/tiff")
|
||||
resolver.RegisterMIMEType(".tif", "image/tiff")
|
||||
resolver.RegisterMIMEType(".ico", "image/x-icon")
|
||||
resolver.RegisterMIMEType(".cur", "image/x-icon")
|
||||
|
||||
// Images - Vector
|
||||
resolver.RegisterMIMEType(".svg", "image/svg+xml")
|
||||
resolver.RegisterMIMEType(".svgz", "image/svg+xml")
|
||||
|
||||
// Fonts
|
||||
resolver.RegisterMIMEType(".woff", "font/woff")
|
||||
resolver.RegisterMIMEType(".woff2", "font/woff2")
|
||||
resolver.RegisterMIMEType(".ttf", "font/ttf")
|
||||
resolver.RegisterMIMEType(".otf", "font/otf")
|
||||
resolver.RegisterMIMEType(".eot", "application/vnd.ms-fontobject")
|
||||
|
||||
// Audio
|
||||
resolver.RegisterMIMEType(".mp3", "audio/mpeg")
|
||||
resolver.RegisterMIMEType(".wav", "audio/wav")
|
||||
resolver.RegisterMIMEType(".ogg", "audio/ogg")
|
||||
resolver.RegisterMIMEType(".oga", "audio/ogg")
|
||||
resolver.RegisterMIMEType(".m4a", "audio/mp4")
|
||||
resolver.RegisterMIMEType(".aac", "audio/aac")
|
||||
resolver.RegisterMIMEType(".flac", "audio/flac")
|
||||
resolver.RegisterMIMEType(".opus", "audio/opus")
|
||||
resolver.RegisterMIMEType(".weba", "audio/webm")
|
||||
|
||||
// Video
|
||||
resolver.RegisterMIMEType(".mp4", "video/mp4")
|
||||
resolver.RegisterMIMEType(".webm", "video/webm")
|
||||
resolver.RegisterMIMEType(".ogv", "video/ogg")
|
||||
resolver.RegisterMIMEType(".avi", "video/x-msvideo")
|
||||
resolver.RegisterMIMEType(".mpeg", "video/mpeg")
|
||||
resolver.RegisterMIMEType(".mpg", "video/mpeg")
|
||||
resolver.RegisterMIMEType(".mov", "video/quicktime")
|
||||
resolver.RegisterMIMEType(".wmv", "video/x-ms-wmv")
|
||||
resolver.RegisterMIMEType(".flv", "video/x-flv")
|
||||
resolver.RegisterMIMEType(".mkv", "video/x-matroska")
|
||||
resolver.RegisterMIMEType(".m4v", "video/mp4")
|
||||
|
||||
// Data & Configuration
|
||||
resolver.RegisterMIMEType(".json", "application/json")
|
||||
resolver.RegisterMIMEType(".xml", "application/xml")
|
||||
resolver.RegisterMIMEType(".yml", "application/yaml")
|
||||
resolver.RegisterMIMEType(".yaml", "application/yaml")
|
||||
resolver.RegisterMIMEType(".toml", "application/toml")
|
||||
resolver.RegisterMIMEType(".ini", "text/plain")
|
||||
resolver.RegisterMIMEType(".conf", "text/plain")
|
||||
resolver.RegisterMIMEType(".config", "text/plain")
|
||||
|
||||
// Documents
|
||||
resolver.RegisterMIMEType(".pdf", "application/pdf")
|
||||
resolver.RegisterMIMEType(".doc", "application/msword")
|
||||
resolver.RegisterMIMEType(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
|
||||
resolver.RegisterMIMEType(".xls", "application/vnd.ms-excel")
|
||||
resolver.RegisterMIMEType(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
resolver.RegisterMIMEType(".ppt", "application/vnd.ms-powerpoint")
|
||||
resolver.RegisterMIMEType(".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation")
|
||||
resolver.RegisterMIMEType(".odt", "application/vnd.oasis.opendocument.text")
|
||||
resolver.RegisterMIMEType(".ods", "application/vnd.oasis.opendocument.spreadsheet")
|
||||
resolver.RegisterMIMEType(".odp", "application/vnd.oasis.opendocument.presentation")
|
||||
|
||||
// Archives
|
||||
resolver.RegisterMIMEType(".zip", "application/zip")
|
||||
resolver.RegisterMIMEType(".tar", "application/x-tar")
|
||||
resolver.RegisterMIMEType(".gz", "application/gzip")
|
||||
resolver.RegisterMIMEType(".bz2", "application/x-bzip2")
|
||||
resolver.RegisterMIMEType(".7z", "application/x-7z-compressed")
|
||||
resolver.RegisterMIMEType(".rar", "application/vnd.rar")
|
||||
|
||||
// Text files
|
||||
resolver.RegisterMIMEType(".txt", "text/plain")
|
||||
resolver.RegisterMIMEType(".md", "text/markdown")
|
||||
resolver.RegisterMIMEType(".markdown", "text/markdown")
|
||||
resolver.RegisterMIMEType(".csv", "text/csv")
|
||||
resolver.RegisterMIMEType(".log", "text/plain")
|
||||
|
||||
// Source code (for syntax highlighting in browsers)
|
||||
resolver.RegisterMIMEType(".c", "text/x-c")
|
||||
resolver.RegisterMIMEType(".cpp", "text/x-c++")
|
||||
resolver.RegisterMIMEType(".h", "text/x-c")
|
||||
resolver.RegisterMIMEType(".hpp", "text/x-c++")
|
||||
resolver.RegisterMIMEType(".go", "text/x-go")
|
||||
resolver.RegisterMIMEType(".py", "text/x-python")
|
||||
resolver.RegisterMIMEType(".java", "text/x-java")
|
||||
resolver.RegisterMIMEType(".rs", "text/x-rust")
|
||||
resolver.RegisterMIMEType(".rb", "text/x-ruby")
|
||||
resolver.RegisterMIMEType(".php", "text/x-php")
|
||||
resolver.RegisterMIMEType(".sh", "text/x-shellscript")
|
||||
resolver.RegisterMIMEType(".bash", "text/x-shellscript")
|
||||
resolver.RegisterMIMEType(".sql", "text/x-sql")
|
||||
resolver.RegisterMIMEType(".template.sql", "text/plain")
|
||||
resolver.RegisterMIMEType(".upg", "text/plain")
|
||||
|
||||
// Web Assembly
|
||||
resolver.RegisterMIMEType(".wasm", "application/wasm")
|
||||
|
||||
// Manifest & Service Worker
|
||||
resolver.RegisterMIMEType(".webmanifest", "application/manifest+json")
|
||||
resolver.RegisterMIMEType(".manifest", "text/cache-manifest")
|
||||
|
||||
// 3D Models
|
||||
resolver.RegisterMIMEType(".gltf", "model/gltf+json")
|
||||
resolver.RegisterMIMEType(".glb", "model/gltf-binary")
|
||||
resolver.RegisterMIMEType(".obj", "model/obj")
|
||||
resolver.RegisterMIMEType(".stl", "model/stl")
|
||||
|
||||
// Other common web assets
|
||||
resolver.RegisterMIMEType(".map", "application/json") // Source maps
|
||||
resolver.RegisterMIMEType(".swf", "application/x-shockwave-flash")
|
||||
resolver.RegisterMIMEType(".apk", "application/vnd.android.package-archive")
|
||||
resolver.RegisterMIMEType(".dmg", "application/x-apple-diskimage")
|
||||
resolver.RegisterMIMEType(".exe", "application/x-msdownload")
|
||||
resolver.RegisterMIMEType(".iso", "application/x-iso9660-image")
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
// GetMIMEType returns the MIME type for the given file path.
|
||||
// It first checks custom registered types, then falls back to Go's mime.TypeByExtension.
|
||||
func (r *DefaultMIMEResolver) GetMIMEType(filePath string) string {
|
||||
ext := strings.ToLower(path.Ext(filePath))
|
||||
|
||||
// Check custom types first
|
||||
r.mu.RLock()
|
||||
if mimeType, ok := r.customTypes[ext]; ok {
|
||||
r.mu.RUnlock()
|
||||
return mimeType
|
||||
}
|
||||
r.mu.RUnlock()
|
||||
|
||||
// Fall back to standard library
|
||||
if mimeType := mime.TypeByExtension(ext); mimeType != "" {
|
||||
return mimeType
|
||||
}
|
||||
|
||||
// Return empty string if unknown
|
||||
return ""
|
||||
}
|
||||
|
||||
// RegisterMIMEType registers a custom MIME type for the given file extension.
|
||||
func (r *DefaultMIMEResolver) RegisterMIMEType(extension, mimeType string) {
|
||||
if !strings.HasPrefix(extension, ".") {
|
||||
extension = "." + extension
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
r.customTypes[strings.ToLower(extension)] = mimeType
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// ConfigurableMIMEResolver implements a MIME type resolver with user-defined mappings only.
|
||||
// It does not use any default mappings.
|
||||
type ConfigurableMIMEResolver struct {
|
||||
types map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewConfigurableMIMEResolver creates a new ConfigurableMIMEResolver with the given mappings.
|
||||
func NewConfigurableMIMEResolver(types map[string]string) *ConfigurableMIMEResolver {
|
||||
resolver := &ConfigurableMIMEResolver{
|
||||
types: make(map[string]string),
|
||||
}
|
||||
|
||||
for ext, mimeType := range types {
|
||||
resolver.RegisterMIMEType(ext, mimeType)
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
// GetMIMEType returns the MIME type for the given file path.
|
||||
func (r *ConfigurableMIMEResolver) GetMIMEType(filePath string) string {
|
||||
ext := strings.ToLower(path.Ext(filePath))
|
||||
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if mimeType, ok := r.types[ext]; ok {
|
||||
return mimeType
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// RegisterMIMEType registers a MIME type for the given file extension.
|
||||
func (r *ConfigurableMIMEResolver) RegisterMIMEType(extension, mimeType string) {
|
||||
if !strings.HasPrefix(extension, ".") {
|
||||
extension = "." + extension
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
r.types[strings.ToLower(extension)] = mimeType
|
||||
r.mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user