github.com/pachyderm/pachyderm@v1.13.4/src/server/http/http.go (about)

     1  package http
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httputil"
     7  	"net/url"
     8  	"path"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/pachyderm/pachyderm/src/client"
    13  	"github.com/pachyderm/pachyderm/src/client/auth"
    14  	"github.com/pachyderm/pachyderm/src/server/pkg/errutil"
    15  
    16  	"github.com/gogo/protobuf/types"
    17  	"github.com/julienschmidt/httprouter"
    18  	"golang.org/x/net/context"
    19  	"google.golang.org/grpc/metadata"
    20  )
    21  
    22  // HTTPPort specifies the port the server will listen on
    23  const apiVersion = "v1"
    24  
    25  func versionPath(p string) string {
    26  	return path.Join("/", apiVersion, p)
    27  }
    28  
    29  var (
    30  	getFilePath = versionPath("pfs/repos/:repoName/commits/:commitID/files/*filePath")
    31  	servicePath = versionPath("pps/services/:serviceName/*path")
    32  	loginPath   = versionPath("auth/login")
    33  	logoutPath  = versionPath("auth/logout")
    34  )
    35  
    36  type router = *httprouter.Router
    37  
    38  type server struct {
    39  	router
    40  	address        string
    41  	pachClient     *client.APIClient
    42  	pachClientOnce sync.Once
    43  	httpClient     *http.Client
    44  }
    45  
    46  // NewHTTPServer returns a Pachyderm HTTP server.
    47  func NewHTTPServer(address string) (http.Handler, error) {
    48  	router := httprouter.New()
    49  	s := &server{
    50  		router:     router,
    51  		address:    address,
    52  		httpClient: &http.Client{},
    53  	}
    54  
    55  	router.GET(getFilePath, s.getFileHandler)
    56  	router.GET(servicePath, s.serviceHandler)
    57  
    58  	router.POST(loginPath, s.authLoginHandler)
    59  	router.POST(logoutPath, s.authLogoutHandler)
    60  	router.POST(servicePath, s.serviceHandler)
    61  
    62  	router.NotFound = http.HandlerFunc(notFound)
    63  	return s, nil
    64  }
    65  
    66  func (s *server) getFileHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    67  	filePaths := strings.Split(ps.ByName("filePath"), "/")
    68  	fileName := filePaths[len(filePaths)-1]
    69  	ctx := context.Background()
    70  	for _, cookie := range r.Cookies() {
    71  		if cookie.Name == auth.ContextTokenKey {
    72  			ctx = metadata.NewIncomingContext(
    73  				ctx,
    74  				metadata.Pairs(auth.ContextTokenKey, cookie.Value),
    75  			)
    76  		}
    77  	}
    78  	downloadValues := r.URL.Query()["download"]
    79  	if len(downloadValues) == 1 && downloadValues[0] == "true" {
    80  		w.Header().Add("Content-Disposition", fmt.Sprintf("attachment; filename=\"%v\"", fileName))
    81  	}
    82  	c := s.getPachClient().WithCtx(ctx)
    83  	commitInfo, err := c.InspectCommit(ps.ByName("repoName"), ps.ByName("commitID"))
    84  	if err != nil {
    85  		httpError(w, err)
    86  		return
    87  	}
    88  	content, err := c.GetFileReadSeeker(ps.ByName("repoName"), ps.ByName("commitID"), ps.ByName("filePath"))
    89  	if err != nil {
    90  		httpError(w, err)
    91  		return
    92  	}
    93  	modtime, err := types.TimestampFromProto(commitInfo.Finished)
    94  	if err != nil {
    95  		httpError(w, err)
    96  		return
    97  	}
    98  	http.ServeContent(w, r, fileName, modtime, content)
    99  }
   100  
   101  func (s *server) serviceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   102  	c := s.getPachClient()
   103  	serviceName := ps.ByName("serviceName")
   104  	pipelineInfo, err := c.InspectPipeline(serviceName)
   105  	if err != nil {
   106  		httpError(w, err)
   107  		return
   108  	}
   109  	URL, err := url.Parse(fmt.Sprintf("http://%s:%d", pipelineInfo.Service.IP, pipelineInfo.Service.ExternalPort))
   110  	if err != nil {
   111  		http.Error(w, err.Error(), http.StatusInternalServerError)
   112  		return
   113  	}
   114  	proxy := httputil.NewSingleHostReverseProxy(URL)
   115  	director := proxy.Director
   116  	proxy.Director = func(req *http.Request) {
   117  		director(req)
   118  		req.URL.Path = strings.TrimPrefix(req.URL.Path, path.Join(path.Dir(path.Dir(servicePath)), serviceName))
   119  	}
   120  	proxy.ServeHTTP(w, r)
   121  }
   122  
   123  func (s *server) authLoginHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   124  	token := r.FormValue("Token")
   125  	if token == "" {
   126  		http.Error(w, "empty token provided", http.StatusInternalServerError)
   127  		return
   128  	}
   129  	w.Header().Add("Set-Cookie", fmt.Sprintf("%v=%v;path=/", auth.ContextTokenKey,
   130  		token))
   131  	w.Header().Add("Access-Control-Allow-Origin", "*")
   132  	w.WriteHeader(http.StatusOK)
   133  }
   134  
   135  func (s *server) authLogoutHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   136  	w.Header().Add("Set-Cookie", fmt.Sprintf("%v=;path=/", auth.ContextTokenKey))
   137  	w.Header().Add("Access-Control-Allow-Origin", "*")
   138  	w.WriteHeader(http.StatusOK)
   139  }
   140  
   141  func notFound(w http.ResponseWriter, r *http.Request) {
   142  	http.Error(w, "route not found", http.StatusNotFound)
   143  }
   144  
   145  func httpError(w http.ResponseWriter, err error) {
   146  	if errutil.IsNotFoundError(err) {
   147  		http.Error(w, err.Error(), http.StatusNotFound)
   148  	} else {
   149  		http.Error(w, err.Error(), http.StatusInternalServerError)
   150  	}
   151  }
   152  
   153  func (s *server) getPachClient() *client.APIClient {
   154  	s.pachClientOnce.Do(func() {
   155  		var err error
   156  		s.pachClient, err = client.NewFromAddress(s.address)
   157  		if err != nil {
   158  			panic(fmt.Sprintf("http server failed to initialize pach client: %v", err))
   159  		}
   160  	})
   161  	return s.pachClient
   162  }