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 }