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  }