func tourRunFunc(req cmds.Request, res cmds.Response) { cfg, err := req.Context().GetConfig() if err != nil { res.SetError(err, cmds.ErrNormal) return } id := tour.TopicID(cfg.Tour.Last) if len(req.Arguments()) > 0 { id = tour.TopicID(req.Arguments()[0]) } w := new(bytes.Buffer) t, err := tourGet(id) if err != nil { // If no topic exists for this id, we handle this error right here. // To help the user achieve the task, we construct a response // comprised of... // 1) a simple error message // 2) the full list of topics fmt.Fprintln(w, "ERROR") fmt.Fprintln(w, err) fmt.Fprintln(w, "") fprintTourList(w, tour.TopicID(cfg.Tour.Last)) res.SetOutput(w) return } fprintTourShow(w, t) res.SetOutput(w) }
//collects options and opens the fuse mountpoint func mountFuse(req cmds.Request) error { cfg, err := req.Context().GetConfig() if err != nil { return fmt.Errorf("mountFuse: GetConfig() failed: %s", err) } fsdir, found, err := req.Option(ipfsMountKwd).String() if err != nil { return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipfsMountKwd, err) } if !found { fsdir = cfg.Mounts.IPFS } nsdir, found, err := req.Option(ipnsMountKwd).String() if err != nil { return fmt.Errorf("mountFuse: req.Option(%s) failed: %s", ipnsMountKwd, err) } if !found { nsdir = cfg.Mounts.IPNS } node, err := req.Context().ConstructNode() if err != nil { return fmt.Errorf("mountFuse: ConstructNode() failed: %s", err) } err = commands.Mount(node, fsdir, nsdir) if err != nil { return err } fmt.Printf("IPFS mounted at: %s\n", fsdir) fmt.Printf("IPNS mounted at: %s\n", nsdir) return nil }
// serveHTTPGateway collects options, creates listener, prints status message and starts serving requests func serveHTTPGateway(req cmds.Request) (error, <-chan error) { cfg, err := req.Context().GetConfig() if err != nil { return fmt.Errorf("serveHTTPGateway: GetConfig() failed: %s", err), nil } gatewayMaddr, err := ma.NewMultiaddr(cfg.Addresses.Gateway) if err != nil { return fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", cfg.Addresses.Gateway, err), nil } writable, writableOptionFound, err := req.Option(writableKwd).Bool() if err != nil { return fmt.Errorf("serveHTTPGateway: req.Option(%s) failed: %s", writableKwd, err), nil } if !writableOptionFound { writable = cfg.Gateway.Writable } gwLis, err := manet.Listen(gatewayMaddr) if err != nil { return fmt.Errorf("serveHTTPGateway: manet.Listen(%s) failed: %s", gatewayMaddr, err), nil } // we might have listened to /tcp/0 - lets see what we are listing on gatewayMaddr = gwLis.Multiaddr() if writable { fmt.Printf("Gateway (writable) server listening on %s\n", gatewayMaddr) } else { fmt.Printf("Gateway (readonly) server listening on %s\n", gatewayMaddr) } var opts = []corehttp.ServeOption{ corehttp.VersionOption(), corehttp.IPNSHostnameOption(), corehttp.GatewayOption(writable), } if len(cfg.Gateway.RootRedirect) > 0 { opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) } node, err := req.Context().ConstructNode() if err != nil { return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil } errc := make(chan error) go func() { errc <- corehttp.Serve(node, gwLis.NetListener(), opts...) close(errc) }() return nil, errc }
func maybeRunGC(req cmds.Request, node *core.IpfsNode) (error, <-chan error) { enableGC, _, err := req.Option(enableGCKwd).Bool() if err != nil { return err, nil } if !enableGC { return nil, nil } errc := make(chan error) go func() { errc <- corerepo.PeriodicGC(req.Context(), node) close(errc) }() return nil, errc }
func setDataCaller(req cmds.Request, root *dag.Node) (key.Key, error) { if len(req.Arguments()) < 3 { return "", fmt.Errorf("not enough arguments for set-data") } nd, err := req.Context().GetNode() if err != nil { return "", err } root.Data = []byte(req.Arguments()[2]) newkey, err := nd.DAG.Add(root) if err != nil { return "", err } return newkey, nil }
func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) { if len(req.Arguments()) < 4 { return "", fmt.Errorf("not enough arguments for add-link") } nd, err := req.InvocContext().GetNode() if err != nil { return "", err } path := req.Arguments()[2] childk := key.B58KeyDecode(req.Arguments()[3]) create, _, err := req.Option("create").Bool() if err != nil { return "", err } var createfunc func() *dag.Node if create { createfunc = func() *dag.Node { return &dag.Node{Data: ft.FolderPBData()} } } e := dagutils.NewDagEditor(nd.DAG, root) childnd, err := nd.DAG.Get(req.Context(), childk) if err != nil { return "", err } err = e.InsertNodeAtPath(req.Context(), path, childnd, createfunc) if err != nil { return "", err } nnode := e.GetNode() return nnode.Key() }
func addLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) { if len(req.Arguments()) < 4 { return "", fmt.Errorf("not enough arguments for add-link") } nd, err := req.InvocContext().GetNode() if err != nil { return "", err } path := req.Arguments()[2] childk := key.B58KeyDecode(req.Arguments()[3]) parts := strings.Split(path, "/") nnode, err := insertNodeAtPath(req.Context(), nd.DAG, root, parts, childk) if err != nil { return "", err } return nnode.Key() }
// commandShouldRunOnDaemon determines, from commmand details, whether a // command ought to be executed on an IPFS daemon. // // It returns true if the command should be executed on a daemon and false if // it should be executed on a client. It returns an error if the command must // NOT be executed on either. func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (bool, error) { path := req.Path() // root command. if len(path) < 1 { return false, nil } if details.cannotRunOnClient && details.cannotRunOnDaemon { return false, fmt.Errorf("command disabled: %s", path[0]) } if details.doesNotUseRepo && details.canRunOnClient() { return false, nil } // at this point need to know whether daemon is running. we defer // to this point so that some commands dont open files unnecessarily. daemonLocked, err := fsrepo.LockedByOtherProcess(req.Context().ConfigRoot) if err != nil { return false, err } if daemonLocked { log.Info("a daemon is running...") if details.cannotRunOnDaemon { e := "ipfs daemon is running. please stop it to run this command" return false, cmds.ClientError(e) } return true, nil } if details.cannotRunOnClient { return false, cmds.ClientError("must run on the ipfs daemon") } return false, nil }
func getBlockForKey(req cmds.Request, skey string) (blocks.Block, error) { if len(skey) == 0 { return nil, fmt.Errorf("zero length cid invalid") } n, err := req.InvocContext().GetNode() if err != nil { return nil, err } c, err := cid.Decode(skey) if err != nil { return nil, err } b, err := n.Blocks.GetBlock(req.Context(), c) if err != nil { return nil, err } log.Debugf("ipfs block: got block with key: %s", b.Cid()) return b, nil }
func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) { if len(req.Arguments()) < 3 { return "", fmt.Errorf("not enough arguments for rm-link") } nd, err := req.InvocContext().GetNode() if err != nil { return "", err } path := req.Arguments()[2] e := dagutils.NewDagEditor(nd.DAG, root) err = e.RmLink(req.Context(), path) if err != nil { return "", err } nnode := e.GetNode() return nnode.Key() }
// read json objects off of the given stream, and write the objects out to // the 'out' channel func readStreamedJson(req cmds.Request, rr io.Reader, out chan<- interface{}) { defer close(out) dec := json.NewDecoder(rr) outputType := reflect.TypeOf(req.Command().Type) ctx := req.Context() for { v, err := decodeTypedVal(outputType, dec) if err != nil { if err != io.EOF { log.Error(err) } return } select { case <-ctx.Done(): return case out <- v: } } }
// read json objects off of the given stream, and write the objects out to // the 'out' channel func readStreamedJson(req cmds.Request, rr io.Reader, out chan<- interface{}) { defer close(out) dec := json.NewDecoder(rr) outputType := reflect.TypeOf(req.Command().Type) ctx := req.Context() for { v, err := decodeTypedVal(outputType, dec) if err != nil { // reading on a closed response body is as good as an io.EOF here if !(strings.Contains(err.Error(), "read on closed response body") || err == io.EOF) { log.Error(err) } return } select { case <-ctx.Done(): return case out <- v: } } }
func rmLinkCaller(req cmds.Request, root *dag.Node) (key.Key, error) { if len(req.Arguments()) < 3 { return "", fmt.Errorf("not enough arguments for rm-link") } nd, err := req.Context().GetNode() if err != nil { return "", err } name := req.Arguments()[2] err = root.RemoveNodeLink(name) if err != nil { return "", err } newkey, err := nd.DAG.Add(root) if err != nil { return "", err } return newkey, nil }
func getBlockForKey(req cmds.Request, skey string) (blocks.Block, error) { n, err := req.InvocContext().GetNode() if err != nil { return nil, err } if !u.IsValidHash(skey) { return nil, errors.New("Not a valid hash") } h, err := mh.FromB58String(skey) if err != nil { return nil, err } k := key.Key(h) b, err := n.Blocks.GetBlock(req.Context(), k) if err != nil { return nil, err } log.Debugf("ipfs block: got block with key: %q", b.Key()) return b, nil }
// getResponse decodes a http.Response to create a cmds.Response func getResponse(httpRes *http.Response, req cmds.Request) (cmds.Response, error) { var err error res := cmds.NewResponse(req) contentType := httpRes.Header.Get(contentTypeHeader) contentType = strings.Split(contentType, ";")[0] lengthHeader := httpRes.Header.Get(contentLengthHeader) if len(lengthHeader) > 0 { length, err := strconv.ParseUint(lengthHeader, 10, 64) if err != nil { return nil, err } res.SetLength(length) } if len(httpRes.Header.Get(streamHeader)) > 0 { // if output is a stream, we can just use the body reader res.SetOutput(httpRes.Body) return res, nil } else if len(httpRes.Header.Get(channelHeader)) > 0 { // if output is coming from a channel, decode each chunk outChan := make(chan interface{}) go func() { dec := json.NewDecoder(httpRes.Body) outputType := reflect.TypeOf(req.Command().Type) ctx := req.Context().Context for { var v interface{} var err error if outputType != nil { v = reflect.New(outputType).Interface() err = dec.Decode(v) } else { err = dec.Decode(&v) } if err != nil && err != io.EOF { fmt.Println(err.Error()) return } select { case <-ctx.Done(): close(outChan) return default: } if err == io.EOF { close(outChan) return } outChan <- v } }() res.SetOutput((<-chan interface{})(outChan)) return res, nil } dec := json.NewDecoder(httpRes.Body) if httpRes.StatusCode >= http.StatusBadRequest { e := cmds.Error{} if httpRes.StatusCode == http.StatusNotFound { // handle 404s e.Message = "Command not found." e.Code = cmds.ErrClient } else if contentType == "text/plain" { // handle non-marshalled errors buf := bytes.NewBuffer(nil) io.Copy(buf, httpRes.Body) e.Message = string(buf.Bytes()) e.Code = cmds.ErrNormal } else { // handle marshalled errors err = dec.Decode(&e) if err != nil { return nil, err } } res.SetError(e, e.Code) } else { outputType := reflect.TypeOf(req.Command().Type) var v interface{} if outputType != nil { v = reflect.New(outputType).Interface() err = dec.Decode(v) } else { err = dec.Decode(&v) } if err != nil && err != io.EOF { return nil, err } if v != nil { res.SetOutput(v) } } return res, nil }
func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd *cmds.Command) (cmds.Response, error) { log.Info(config.EnvDir, " ", req.Context().ConfigRoot) var res cmds.Response req.Context().Context = ctx details, err := commandDetails(req.Path(), root) if err != nil { return nil, err } log.Debug("looking for running daemon...") useDaemon, err := commandShouldRunOnDaemon(*details, req, root) if err != nil { return nil, err } err = callPreCommandHooks(ctx, *details, req, root) if err != nil { return nil, err } if cmd.PreRun != nil { err = cmd.PreRun(req) if err != nil { return nil, err } } if useDaemon { cfg, err := req.Context().GetConfig() if err != nil { return nil, err } addr, err := ma.NewMultiaddr(cfg.Addresses.API) if err != nil { return nil, err } log.Infof("Executing command on daemon running at %s", addr) _, host, err := manet.DialArgs(addr) if err != nil { return nil, err } client := cmdsHttp.NewClient(host) res, err = client.Send(req) if err != nil { return nil, err } } else { log.Debug("Executing command locally") // Okay!!!!! NOW we can call the command. res = root.Call(req) } if cmd.PostRun != nil { cmd.PostRun(req, res) } return res, nil }
func daemonFunc(req cmds.Request, res cmds.Response) { // let the user know we're going. fmt.Printf("Initializing daemon...\n") ctx := req.InvocContext() go func() { select { case <-req.Context().Done(): fmt.Println("Received interrupt signal, shutting down...") } }() // check transport encryption flag. unencrypted, _, _ := req.Option(unencryptTransportKwd).Bool() if unencrypted { log.Warningf(`Running with --%s: All connections are UNENCRYPTED. You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd) conn.EncryptConnections = false } // first, whether user has provided the initialization flag. we may be // running in an uninitialized state. initialize, _, err := req.Option(initOptionKwd).Bool() if err != nil { res.SetError(err, cmds.ErrNormal) return } if initialize { // now, FileExists is our best method of detecting whether IPFS is // configured. Consider moving this into a config helper method // `IsInitialized` where the quality of the signal can be improved over // time, and many call-sites can benefit. if !util.FileExists(req.InvocContext().ConfigRoot) { err := initWithDefaults(os.Stdout, req.InvocContext().ConfigRoot) if err != nil { res.SetError(err, cmds.ErrNormal) return } } } // acquire the repo lock _before_ constructing a node. we need to make // sure we are permitted to access the resources (datastore, etc.) repo, err := fsrepo.Open(req.InvocContext().ConfigRoot) if err != nil { res.SetError(err, cmds.ErrNormal) return } cfg, err := ctx.GetConfig() if err != nil { res.SetError(err, cmds.ErrNormal) return } // Start assembling node config ncfg := &core.BuildCfg{ Online: true, Repo: repo, } routingOption, _, err := req.Option(routingOptionKwd).String() if err != nil { res.SetError(err, cmds.ErrNormal) return } if routingOption == routingOptionSupernodeKwd { servers, err := repo.Config().SupernodeRouting.ServerIPFSAddrs() if err != nil { res.SetError(err, cmds.ErrNormal) repo.Close() // because ownership hasn't been transferred to the node return } var infos []peer.PeerInfo for _, addr := range servers { infos = append(infos, peer.PeerInfo{ ID: addr.ID(), Addrs: []ma.Multiaddr{addr.Transport()}, }) } ncfg.Routing = corerouting.SupernodeClient(infos...) } node, err := core.NewNode(req.Context(), ncfg) if err != nil { log.Error("error from node construction: ", err) res.SetError(err, cmds.ErrNormal) return } printSwarmAddrs(node) defer func() { // We wait for the node to close first, as the node has children // that it will wait for before closing, such as the API server. node.Close() select { case <-req.Context().Done(): log.Info("Gracefully shut down daemon") default: } }() req.InvocContext().ConstructNode = func() (*core.IpfsNode, error) { return node, nil } // construct api endpoint - every time err, apiErrc := serveHTTPApi(req) if err != nil { res.SetError(err, cmds.ErrNormal) return } // construct http gateway - if it is set in the config var gwErrc <-chan error if len(cfg.Addresses.Gateway) > 0 { var err error err, gwErrc = serveHTTPGateway(req) if err != nil { res.SetError(err, cmds.ErrNormal) return } } // construct fuse mountpoints - if the user provided the --mount flag mount, _, err := req.Option(mountKwd).Bool() if err != nil { res.SetError(err, cmds.ErrNormal) return } if mount { if err := mountFuse(req); err != nil { res.SetError(err, cmds.ErrNormal) return } } fmt.Printf("Daemon is ready\n") // collect long-running errors and block for shutdown // TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown for err := range merge(apiErrc, gwErrc) { if err != nil { res.SetError(err, cmds.ErrNormal) return } } }
func daemonFunc(req cmds.Request, res cmds.Response) { // Inject metrics before we do anything err := mprome.Inject() if err != nil { log.Errorf("Injecting prometheus handler for metrics failed with message: %s\n", err.Error()) } // let the user know we're going. fmt.Printf("Initializing daemon...\n") managefd, _, _ := req.Option(adjustFDLimitKwd).Bool() if managefd { if err := fileDescriptorCheck(); err != nil { log.Errorf("setting file descriptor limit: %s", err) } } ctx := req.InvocContext() go func() { select { case <-req.Context().Done(): fmt.Println("Received interrupt signal, shutting down...") fmt.Println("(Hit ctrl-c again to force-shutdown the daemon.)") } }() // check transport encryption flag. unencrypted, _, _ := req.Option(unencryptTransportKwd).Bool() if unencrypted { log.Warningf(`Running with --%s: All connections are UNENCRYPTED. You will not be able to connect to regular encrypted networks.`, unencryptTransportKwd) iconn.EncryptConnections = false } // first, whether user has provided the initialization flag. we may be // running in an uninitialized state. initialize, _, err := req.Option(initOptionKwd).Bool() if err != nil { res.SetError(err, cmds.ErrNormal) return } if initialize { // now, FileExists is our best method of detecting whether ipfs is // configured. Consider moving this into a config helper method // `IsInitialized` where the quality of the signal can be improved over // time, and many call-sites can benefit. if !util.FileExists(req.InvocContext().ConfigRoot) { err := initWithDefaults(os.Stdout, req.InvocContext().ConfigRoot) if err != nil { res.SetError(err, cmds.ErrNormal) return } } } // acquire the repo lock _before_ constructing a node. we need to make // sure we are permitted to access the resources (datastore, etc.) repo, err := fsrepo.Open(req.InvocContext().ConfigRoot) switch err { default: res.SetError(err, cmds.ErrNormal) return case fsrepo.ErrNeedMigration: domigrate, found, _ := req.Option(migrateKwd).Bool() fmt.Println("Found outdated fs-repo, migrations need to be run.") if !found { domigrate = YesNoPrompt("Run migrations now? [y/N]") } if !domigrate { fmt.Println("Not running migrations of fs-repo now.") fmt.Println("Please get fs-repo-migrations from https://dist.ipfs.io") res.SetError(fmt.Errorf("fs-repo requires migration"), cmds.ErrNormal) return } err = migrate.RunMigration(fsrepo.RepoVersion) if err != nil { fmt.Println("The migrations of fs-repo failed:") fmt.Printf(" %s\n", err) fmt.Println("If you think this is a bug, please file an issue and include this whole log output.") fmt.Println(" https://github.com/ipfs/fs-repo-migrations") res.SetError(err, cmds.ErrNormal) return } repo, err = fsrepo.Open(req.InvocContext().ConfigRoot) if err != nil { res.SetError(err, cmds.ErrNormal) return } case nil: break } cfg, err := ctx.GetConfig() if err != nil { res.SetError(err, cmds.ErrNormal) return } offline, _, _ := req.Option(offlineKwd).Bool() pubsub, _, _ := req.Option(enableFloodSubKwd).Bool() // Start assembling node config ncfg := &core.BuildCfg{ Repo: repo, Permament: true, // It is temporary way to signify that node is permament Online: !offline, ExtraOpts: map[string]bool{ "pubsub": pubsub, }, //TODO(Kubuxu): refactor Online vs Offline by adding Permanent vs Ephemeral } routingOption, _, err := req.Option(routingOptionKwd).String() if err != nil { res.SetError(err, cmds.ErrNormal) return } switch routingOption { case routingOptionSupernodeKwd: servers, err := cfg.SupernodeRouting.ServerIPFSAddrs() if err != nil { res.SetError(err, cmds.ErrNormal) repo.Close() // because ownership hasn't been transferred to the node return } var infos []pstore.PeerInfo for _, addr := range servers { infos = append(infos, pstore.PeerInfo{ ID: addr.ID(), Addrs: []ma.Multiaddr{addr.Transport()}, }) } ncfg.Routing = corerouting.SupernodeClient(infos...) case routingOptionDHTClientKwd: ncfg.Routing = core.DHTClientOption } node, err := core.NewNode(req.Context(), ncfg) if err != nil { log.Error("error from node construction: ", err) res.SetError(err, cmds.ErrNormal) return } node.SetLocal(false) printSwarmAddrs(node) defer func() { // We wait for the node to close first, as the node has children // that it will wait for before closing, such as the API server. node.Close() select { case <-req.Context().Done(): log.Info("Gracefully shut down daemon") default: } }() req.InvocContext().ConstructNode = func() (*core.IpfsNode, error) { return node, nil } // construct api endpoint - every time err, apiErrc := serveHTTPApi(req) if err != nil { res.SetError(err, cmds.ErrNormal) return } // construct http gateway - if it is set in the config var gwErrc <-chan error if len(cfg.Addresses.Gateway) > 0 { var err error err, gwErrc = serveHTTPGateway(req) if err != nil { res.SetError(err, cmds.ErrNormal) return } } // construct fuse mountpoints - if the user provided the --mount flag mount, _, err := req.Option(mountKwd).Bool() if err != nil { res.SetError(err, cmds.ErrNormal) return } if mount && offline { res.SetError(errors.New("mount is not currently supported in offline mode"), cmds.ErrClient) return } if mount { if err := mountFuse(req); err != nil { res.SetError(err, cmds.ErrNormal) return } } // repo blockstore GC - if --enable-gc flag is present err, gcErrc := maybeRunGC(req, node) if err != nil { res.SetError(err, cmds.ErrNormal) return } // initialize metrics collector prometheus.MustRegister(&corehttp.IpfsNodeCollector{Node: node}) fmt.Printf("Daemon is ready\n") // collect long-running errors and block for shutdown // TODO(cryptix): our fuse currently doesnt follow this pattern for graceful shutdown for err := range merge(apiErrc, gwErrc, gcErrc) { if err != nil { log.Error(err) res.SetError(err, cmds.ErrNormal) } } return }
func (c *client) Send(req cmds.Request) (cmds.Response, error) { if req.Context() == nil { log.Warningf("no context set in request") if err := req.SetRootContext(context.TODO()); err != nil { return nil, err } } // save user-provided encoding previousUserProvidedEncoding, found, err := req.Option(cmds.EncShort).String() if err != nil { return nil, err } // override with json to send to server req.SetOption(cmds.EncShort, cmds.JSON) // stream channel output req.SetOption(cmds.ChanOpt, "true") query, err := getQuery(req) if err != nil { return nil, err } var fileReader *MultiFileReader var reader io.Reader if req.Files() != nil { fileReader = NewMultiFileReader(req.Files(), true) reader = fileReader } path := strings.Join(req.Path(), "/") url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, path, query) httpReq, err := http.NewRequest("POST", url, reader) if err != nil { return nil, err } // TODO extract string consts? if fileReader != nil { httpReq.Header.Set(contentTypeHeader, "multipart/form-data; boundary="+fileReader.Boundary()) httpReq.Header.Set(contentDispHeader, "form-data: name=\"files\"") } else { httpReq.Header.Set(contentTypeHeader, applicationOctetStream) } httpReq.Header.Set(uaHeader, config.ApiVersion) ec := make(chan error, 1) rc := make(chan cmds.Response, 1) dc := req.Context().Done() go func() { httpRes, err := c.httpClient.Do(httpReq) if err != nil { ec <- err return } // using the overridden JSON encoding in request res, err := getResponse(httpRes, req) if err != nil { ec <- err return } rc <- res }() for { select { case <-dc: log.Debug("Context cancelled, cancelling HTTP request...") tr := http.DefaultTransport.(*http.Transport) tr.CancelRequest(httpReq) dc = nil // Wait for ec or rc case err := <-ec: return nil, err case res := <-rc: if found && len(previousUserProvidedEncoding) > 0 { // reset to user provided encoding after sending request // NB: if user has provided an encoding but it is the empty string, // still leave it as JSON. req.SetOption(cmds.EncShort, previousUserProvidedEncoding) } return res, nil } } }
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests func serveHTTPApi(req cmds.Request) (error, <-chan error) { cfg, err := req.Context().GetConfig() if err != nil { return fmt.Errorf("serveHTTPApi: GetConfig() failed: %s", err), nil } apiMaddr, err := ma.NewMultiaddr(cfg.Addresses.API) if err != nil { return fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", cfg.Addresses.API, err), nil } apiLis, err := manet.Listen(apiMaddr) if err != nil { return fmt.Errorf("serveHTTPApi: manet.Listen(%s) failed: %s", apiMaddr, err), nil } // we might have listened to /tcp/0 - lets see what we are listing on apiMaddr = apiLis.Multiaddr() fmt.Printf("API server listening on %s\n", apiMaddr) unrestricted, _, err := req.Option(unrestrictedApiAccessKwd).Bool() if err != nil { return fmt.Errorf("serveHTTPApi: Option(%s) failed: %s", unrestrictedApiAccessKwd, err), nil } apiGw := corehttp.NewGateway(corehttp.GatewayConfig{ Writable: true, BlockList: &corehttp.BlockList{ Decider: func(s string) bool { if unrestricted { return true } // for now, only allow paths in the WebUI path for _, webuipath := range corehttp.WebUIPaths { if strings.HasPrefix(s, webuipath) { return true } } return false }, }, }) var opts = []corehttp.ServeOption{ corehttp.CommandsOption(*req.Context()), corehttp.WebUIOption, apiGw.ServeOption(), corehttp.VersionOption(), defaultMux("/debug/vars"), defaultMux("/debug/pprof/"), corehttp.LogOption(), corehttp.PrometheusOption("/debug/metrics/prometheus"), } if len(cfg.Gateway.RootRedirect) > 0 { opts = append(opts, corehttp.RedirectOption("", cfg.Gateway.RootRedirect)) } node, err := req.Context().ConstructNode() if err != nil { return fmt.Errorf("serveHTTPGateway: ConstructNode() failed: %s", err), nil } errc := make(chan error) go func() { errc <- corehttp.Serve(node, apiLis.NetListener(), opts...) close(errc) }() return nil, errc }
func (c *client) Send(req cmds.Request) (cmds.Response, error) { // save user-provided encoding previousUserProvidedEncoding, found, err := req.Option(cmds.EncShort).String() if err != nil { return nil, err } // override with json to send to server req.SetOption(cmds.EncShort, cmds.JSON) // stream channel output req.SetOption(cmds.ChanOpt, "true") query, err := getQuery(req) if err != nil { return nil, err } var fileReader *MultiFileReader var reader io.Reader if req.Files() != nil { fileReader = NewMultiFileReader(req.Files(), true) reader = fileReader } else { // if we have no file data, use an empty Reader // (http.NewRequest panics when a nil Reader is used) reader = strings.NewReader("") } path := strings.Join(req.Path(), "/") url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, path, query) httpReq, err := http.NewRequest("POST", url, reader) if err != nil { return nil, err } // TODO extract string consts? if fileReader != nil { httpReq.Header.Set("Content-Type", "multipart/form-data; boundary="+fileReader.Boundary()) httpReq.Header.Set("Content-Disposition", "form-data: name=\"files\"") } else { httpReq.Header.Set("Content-Type", "application/octet-stream") } version := config.CurrentVersionNumber httpReq.Header.Set("User-Agent", fmt.Sprintf("/go-ipfs/%s/", version)) ec := make(chan error, 1) rc := make(chan cmds.Response, 1) dc := req.Context().Context.Done() go func() { httpRes, err := http.DefaultClient.Do(httpReq) if err != nil { ec <- err return } // using the overridden JSON encoding in request res, err := getResponse(httpRes, req) if err != nil { ec <- err return } rc <- res }() for { select { case <-dc: log.Debug("Context cancelled, cancelling HTTP request...") tr := http.DefaultTransport.(*http.Transport) tr.CancelRequest(httpReq) dc = nil // Wait for ec or rc case err := <-ec: return nil, err case res := <-rc: if found && len(previousUserProvidedEncoding) > 0 { // reset to user provided encoding after sending request // NB: if user has provided an encoding but it is the empty string, // still leave it as JSON. req.SetOption(cmds.EncShort, previousUserProvidedEncoding) } return res, nil } } }
func (c *client) Send(req cmds.Request) (cmds.Response, error) { if req.Context() == nil { log.Warningf("no context set in request") if err := req.SetRootContext(context.TODO()); err != nil { return nil, err } } // save user-provided encoding previousUserProvidedEncoding, found, err := req.Option(cmds.EncShort).String() if err != nil { return nil, err } // override with json to send to server req.SetOption(cmds.EncShort, cmds.JSON) // stream channel output req.SetOption(cmds.ChanOpt, "true") query, err := getQuery(req) if err != nil { return nil, err } var fileReader *MultiFileReader var reader io.Reader if req.Files() != nil { fileReader = NewMultiFileReader(req.Files(), true) reader = fileReader } path := strings.Join(req.Path(), "/") url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, path, query) httpReq, err := http.NewRequest("POST", url, reader) if err != nil { return nil, err } // TODO extract string consts? if fileReader != nil { httpReq.Header.Set(contentTypeHeader, "multipart/form-data; boundary="+fileReader.Boundary()) } else { httpReq.Header.Set(contentTypeHeader, applicationOctetStream) } httpReq.Header.Set(uaHeader, config.ApiVersion) httpReq.Cancel = req.Context().Done() httpReq.Close = true httpRes, err := c.httpClient.Do(httpReq) if err != nil { return nil, err } // using the overridden JSON encoding in request res, err := getResponse(httpRes, req) if err != nil { return nil, err } if found && len(previousUserProvidedEncoding) > 0 { // reset to user provided encoding after sending request // NB: if user has provided an encoding but it is the empty string, // still leave it as JSON. req.SetOption(cmds.EncShort, previousUserProvidedEncoding) } return res, nil }