github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/services/imageproxy/imageproxy.go (about) 1 // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package imageproxy 5 6 import ( 7 "errors" 8 "io" 9 "net/http" 10 "net/url" 11 "strings" 12 "sync" 13 14 "github.com/xzl8028/xenia-server/mlog" 15 "github.com/xzl8028/xenia-server/model" 16 "github.com/xzl8028/xenia-server/services/configservice" 17 "github.com/xzl8028/xenia-server/services/httpservice" 18 ) 19 20 var ErrNotEnabled = Error{errors.New("imageproxy.ImageProxy: image proxy not enabled")} 21 22 // An ImageProxy is the public interface for Xenia's image proxy. An instance of ImageProxy should be created 23 // using MakeImageProxy which requires a configService and an HTTPService provided by the server. 24 type ImageProxy struct { 25 ConfigService configservice.ConfigService 26 configListenerId string 27 28 HTTPService httpservice.HTTPService 29 30 Logger *mlog.Logger 31 32 lock sync.RWMutex 33 backend ImageProxyBackend 34 } 35 36 // An ImageProxyBackend provides the functionality for different types of image proxies. An ImageProxy will construct 37 // the required backend depending on the ImageProxySettings provided by the ConfigService. 38 type ImageProxyBackend interface { 39 // GetImage provides a proxied image in response to an HTTP request. 40 GetImage(w http.ResponseWriter, r *http.Request, imageURL string) 41 42 // GetImageDirect returns a proxied image along with its content type. 43 GetImageDirect(imageURL string) (io.ReadCloser, string, error) 44 } 45 46 func MakeImageProxy(configService configservice.ConfigService, httpService httpservice.HTTPService, logger *mlog.Logger) *ImageProxy { 47 proxy := &ImageProxy{ 48 ConfigService: configService, 49 HTTPService: httpService, 50 Logger: logger, 51 } 52 53 proxy.configListenerId = proxy.ConfigService.AddConfigListener(proxy.OnConfigChange) 54 55 config := proxy.ConfigService.Config() 56 proxy.backend = proxy.makeBackend(*config.ImageProxySettings.Enable, *config.ImageProxySettings.ImageProxyType) 57 58 return proxy 59 } 60 61 func (proxy *ImageProxy) makeBackend(enable bool, proxyType string) ImageProxyBackend { 62 if !enable { 63 return nil 64 } 65 66 switch proxyType { 67 case model.IMAGE_PROXY_TYPE_LOCAL: 68 return makeLocalBackend(proxy) 69 case model.IMAGE_PROXY_TYPE_ATMOS_CAMO: 70 return makeAtmosCamoBackend(proxy) 71 default: 72 return nil 73 } 74 } 75 76 func (proxy *ImageProxy) Close() { 77 proxy.lock.Lock() 78 defer proxy.lock.Unlock() 79 80 proxy.ConfigService.RemoveConfigListener(proxy.configListenerId) 81 } 82 83 func (proxy *ImageProxy) OnConfigChange(oldConfig, newConfig *model.Config) { 84 if *oldConfig.ImageProxySettings.Enable != *newConfig.ImageProxySettings.Enable || 85 *oldConfig.ImageProxySettings.ImageProxyType != *newConfig.ImageProxySettings.ImageProxyType { 86 proxy.lock.Lock() 87 defer proxy.lock.Unlock() 88 89 proxy.backend = proxy.makeBackend(*newConfig.ImageProxySettings.Enable, *newConfig.ImageProxySettings.ImageProxyType) 90 } 91 } 92 93 // GetImage takes an HTTP request for an image and requests that image using the image proxy. 94 func (proxy *ImageProxy) GetImage(w http.ResponseWriter, r *http.Request, imageURL string) { 95 proxy.lock.RLock() 96 defer proxy.lock.RUnlock() 97 98 if proxy.backend == nil { 99 w.WriteHeader(http.StatusNotImplemented) 100 return 101 } 102 103 proxy.backend.GetImage(w, r, imageURL) 104 } 105 106 // GetImageDirect takes the URL of an image and returns the image along with its content type. 107 func (proxy *ImageProxy) GetImageDirect(imageURL string) (io.ReadCloser, string, error) { 108 proxy.lock.RLock() 109 defer proxy.lock.RUnlock() 110 111 if proxy.backend == nil { 112 return nil, "", ErrNotEnabled 113 } 114 115 return proxy.backend.GetImageDirect(imageURL) 116 } 117 118 // GetProxiedImageURL takes the URL of an image and returns a URL that can be used to view that image through the 119 // image proxy. 120 func (proxy *ImageProxy) GetProxiedImageURL(imageURL string) string { 121 return getProxiedImageURL(imageURL, *proxy.ConfigService.Config().ServiceSettings.SiteURL) 122 } 123 124 func getProxiedImageURL(imageURL, siteURL string) string { 125 if imageURL == "" || imageURL[0] == '/' || strings.HasPrefix(imageURL, siteURL) { 126 return imageURL 127 } 128 129 return siteURL + "/api/v4/image?url=" + url.QueryEscape(imageURL) 130 } 131 132 // GetUnproxiedImageURL takes the URL of an image on the image proxy and returns the original URL of the image. 133 func (proxy *ImageProxy) GetUnproxiedImageURL(proxiedURL string) string { 134 return getUnproxiedImageURL(proxiedURL, *proxy.ConfigService.Config().ServiceSettings.SiteURL) 135 } 136 137 func getUnproxiedImageURL(proxiedURL, siteURL string) string { 138 if !strings.HasPrefix(proxiedURL, siteURL+"/api/v4/image?url=") { 139 return proxiedURL 140 } 141 142 parsed, err := url.Parse(proxiedURL) 143 if err != nil { 144 return proxiedURL 145 } 146 147 u := parsed.Query()["url"] 148 if len(u) == 0 { 149 return proxiedURL 150 } 151 152 return u[0] 153 }