首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于 curl 和 cos 的粘贴板

基于 curl 和 cos 的粘贴板

原创
作者头像
王磊-字节跳动
发布2021-09-25 21:35:21
1.4K0
发布2021-09-25 21:35:21
举报
文章被收录于专栏:01ZOO01ZOO

背景

很多时候,我们需要一个临时的粘贴板,有时候我们可以使用聊天工具作为粘贴板,或者在互联网上找到类似的服务做粘贴使用. 不过这么做显然是有很多限制的,除了不够 geek 之外,有很多场合,我们需要这个粘贴板可以和其他 unix 类工具配合使用,组成更复杂的一些脚本。

那么可不可以做一个基于 curl 的粘贴板工具呢,临时粘贴的内容也比较好处理,存在对象存储里面就好了,这里我们就用腾讯云上的 cos 存储做一个小的工具【cos 的免费额度应该就够我们使用了】

实现

首先这个服务是一个 http 服务,他需要有以下的功能:

  1. 支持写入任意二进制数据
  2. 写入数据后返回一个 粘贴板 id,通过 这个id 可以返回数据
  3. 用 curl 就能使用
  4. 支持定期清理旧的粘贴板数据
  5. 存储数据到 cos 上
  6. 其他,比如 size 限制,qps 限制等

这是一个很简单的工具,实现的代码不超过 200 行

var (
	DefaultTTL = flag.Duration("default_ttl", time.Hour*24*7, "default ttl for object")
	RateLimit  = flag.Int("rate_limit", 1, "rate limit for api call")
	SizeLimit  = flag.Int64("size_limit", 1024*1024*10, "size limit for object in byte")
	OSSecret   = flag.String("os_secret", "::", "secret key for object storage, format: key:id:session")
	BucketUrl  = flag.String("bucket_url", "", "bucket_url for object storage")
	NameLength = flag.Int("name_length", 4, "name length for object put")
)

func main() {
	flag.Parse()
	rand.Seed(time.Now().UnixNano())

	key, secret, token := "", "", ""
	if os.Getenv("os_secret") != "" {
		*OSSecret = os.Getenv("os_secret")
	}
	if os.Getenv("bucket_url") != "" {
		*BucketUrl = os.Getenv("bucket_url")
	}
	secretList := strings.Split(*OSSecret, ":")
	if len(secretList) >= 1 {
		key = secretList[0]
	}
	if len(secretList) >= 2 {
		secret = secretList[1]
	}
	if len(secretList) >= 3 {
		token = secretList[2]
	}

	u, err := url.Parse(*BucketUrl)
	if err != nil {
		panic("bucket url not valid")
	}
	client := cos.NewClient(&cos.BaseURL{BucketURL: u}, &http.Client{
		Transport: &cos.AuthorizationTransport{
			SecretID:     key,
			SecretKey:    secret,
			SessionToken: token,
		},
	})

	go expireJob(client)

	s := &http.Server{
		Addr:           ":80",
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 10,
	}

	limiter := rate.NewLimiter(rate.Limit(*RateLimit), *RateLimit)
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
		defer cancel()

		_ = limiter.Wait(ctx)

		if request.Method == "POST" {
			log.Printf("[Handle] Method=Post Content-Length=%d", request.ContentLength)
			if *SizeLimit > 0 && request.ContentLength > *SizeLimit {
				writer.WriteHeader(400)
				_, _ = writer.Write([]byte(fmt.Sprintf("content size out of limit: %d", *SizeLimit)))
				return
			}
			name := fmt.Sprintf("%s/%s", time.Now().Format("20060102"), randName())
			resp, err := client.Object.Put(ctx, name, request.Body, &cos.ObjectPutOptions{
				ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{},
			})
			if err != nil {
				if resp != nil {
					writer.WriteHeader(resp.StatusCode)
				} else {
					writer.WriteHeader(400)
				}
				log.Printf("[Handle] put file failed: %s", err)
				_, _ = writer.Write([]byte("put file failed"))
				return
			}
			writer.WriteHeader(resp.StatusCode)
			_, _ = writer.Write([]byte(encodeName(name)))
		} else if request.Method == "GET" {
			name, err := decodeName(strings.Trim(request.URL.Path, "/"))
			log.Printf("[Handle] Method=Get Name=%s", name)
			if err != nil {
				writer.WriteHeader(400)
				log.Printf("[Handle] file not valid: %s", err)
				_, _ = writer.Write([]byte("file name not valid"))
				return
			}
			resp, err := client.Object.Get(ctx, name, nil)
			if err != nil {
				if resp != nil {
					writer.WriteHeader(resp.StatusCode)
				} else {
					writer.WriteHeader(400)
				}
				log.Printf("[Handle] get file failed: %s", err)
				_, _ = writer.Write([]byte("get file failed"))
				return
			}
			data, _ := ioutil.ReadAll(resp.Body)
			_, _ = writer.Write(data)
		}
	})
	log.Println("starting server...")
	log.Fatal(s.ListenAndServe())
}

func expireJob(client *cos.Client) {
	for range time.Tick(time.Minute * 10) {
		log.Println("do expire....")
		expireTo := time.Now().Add(-1 * *DefaultTTL)

		var cleaned []string
		for day := 1; day <= 30; day++ {
			toDelDay := expireTo.Add(time.Duration(day) * time.Hour * 24 * -1)
			name := toDelDay.Format("20060102") + "/"
			ctx := context.Background()
			resp, err := client.Object.Head(ctx, name, nil)
			if err != nil {
				if resp != nil && resp.StatusCode == 404 {
					continue
				}
				log.Println("head object failed, ", err)
				continue
			}
			if resp.StatusCode != 200 {
				continue
			}
			resp, err = client.Object.Delete(ctx, name, nil)
			if err != nil {
				log.Println("delete object failed, ", err)
				continue
			}
			if resp.StatusCode != 200 {
				log.Println("delete object failed, ", resp.Status)
			} else {
				cleaned = append(cleaned, name)
				log.Printf("expire old folder: %s success", name)
			}
		}
		log.Println("do expire done:", cleaned)
	}
}

使用 docker 部署,dockerfile 如下

FROM alpine
ADD bin/clipboard /usr/local/bin
ENTRYPOINT ["clipboard"]

演示

# 例子 1
➜ curl 148.70.103.38:30744 -d "hello"
20210925Vd4h%                                                                                                                                                     

➜ curl 148.70.103.38:30744/20210925Vd4h
hello%                                                                                                                                                   


# 例子 2                                                                   
➜ curl 148.70.103.38:30744 -d '{"someJsonData"}'
20210925aHiw%  

➜ curl 148.70.103.38:30744/20210925aHiw
{"someJsonData"}%


# 例子3: 上传一张图片
➜ curl 148.70.103.38:30744 --data-binary @`pwd`/test.png
20210925fzul%                                                                                                                                                                                                               
➜ curl 148.70.103.38:30744/20210925fzul > test1.png
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 70858    0 70858    0     0   162k      0 --:--:-- --:--:-- --:--:--  162k

完整的项目代码在 这里

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 实现
  • 演示
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档