1 Star 0 Fork 1

sysadm/资源管理库

forked from kitfast/资源管理库 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
resource_meta.go 9.40 KB
一键复制 编辑 原始数据 按行查看 历史
package resml
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"mime"
"net/http"
"net/textproto"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
const (
META_CONTENT_LENGTH = "Content-Length" // 内容长度,请求+响应
META_CONTENT_TYPE = "Content-Type" // 内容类型,请求+响应
META_LAST_MODIFIED = "Last-Modified" // 最后修改时间,仅响应
META_ETAG = "ETag" // 实例标识符,仅响应
META_REQ_MODIFIED = "X-Modified-At" // 资源在客户端的修改时间,仅请求
META_REQ_ETAG = "X-ETag" // 资源在客户端的实体标识符,仅请求
MIME_OCTETE_STREAM = "application/octet-stream"
)
// 资源元数据
type Meta interface {
ContentLength() int64 // 资源内容的字节大小,负数表示未知
SetContentLength(int64) // 设置资源的字节大小
ContentType() string // 资源的 MIME 类型,可以为空字符串
SetContentType(string) // 设置资源的 MIME 类型
LastModified() time.Time // 资源的最后修改时间,可以为零值
SetLastModified(time.Time) // 设置资源的最后修改时间
ETag() ETag // 资源的 ETag,ETag 可以为 nil
}
// 资源元数据
type ResourceMeta struct {
contentLength int64
contentType string
lastModified time.Time
etag ETag
}
// 设置资源内容的字节大小
func (m *ResourceMeta) WithContentLength(v int64) *ResourceMeta {
m.SetContentLength(v)
return m
}
// 设置资源的 MIME 类型
func (m *ResourceMeta) WithContentType(v string) *ResourceMeta {
m.contentType = v
return m
}
// 设置资源最后修改时间
func (m *ResourceMeta) WithLastModified(v time.Time) *ResourceMeta {
m.SetLastModified(v)
return m
}
// 设置资源的 ETag
func (m *ResourceMeta) WithETag(v ETag) *ResourceMeta {
m.etag = v
return m
}
// 获取资源内容的字节大小
func (m *ResourceMeta) ContentLength() int64 {
return m.contentLength
}
// 设置资源的字节大小
func (m *ResourceMeta) SetContentLength(v int64) {
m.contentLength = v
}
// 获取资源的 MIME 类型
func (m *ResourceMeta) ContentType() string {
return m.contentType
}
// 设置资源的 MIME 类型
func (m *ResourceMeta) SetContentType(v string) {
if vv := strings.TrimSpace(v); vv != "" {
m.contentType = vv
}
}
// 获取资源最后修改时间
func (m *ResourceMeta) LastModified() time.Time {
return m.lastModified
}
// 设置资源的最后修改时间
func (m *ResourceMeta) SetLastModified(v time.Time) {
m.lastModified = v
}
// 获取资源的 ETag
func (m *ResourceMeta) ETag() ETag {
return m.etag
}
// Meta 的 ETag 与 ContentLength 是否相等
func MetaEtagSizeEquals(a, b Meta) bool {
if a == nil || b == nil || a == b {
return a == b
}
return a.ContentLength() == b.ContentLength() && ETagEquals(a.ETag(), b.ETag())
}
// 判断 Meta 是否相等(时间的对比精确到秒)
func MetaEquals(a, b Meta) bool {
if !MetaEtagSizeEquals(a, b) {
return false
}
return a.ContentType() == b.ContentType() && SecondEquals(a.LastModified(), b.LastModified())
}
// 判断时间是否相等(精确到秒)
func SecondEquals(a, b time.Time) bool {
return a.Unix() == b.Unix()
}
// 写到 HTTP 头。
func MetaSetToHttpHeader(m Meta, h http.Header, isRequest bool) {
if ct := m.ContentType(); ct != "" {
h.Set(META_CONTENT_TYPE, ct)
}
if size := m.ContentLength(); size >= 0 {
h.Set(META_CONTENT_LENGTH, fmt.Sprintf("%d", size))
}
etn, lmn := META_ETAG, META_LAST_MODIFIED
if isRequest {
etn, lmn = META_REQ_ETAG, META_REQ_MODIFIED
}
if etag := m.ETag(); etag != nil {
h.Set(etn, `"`+etag.String()+`"`)
}
if tm := m.LastModified(); !tm.IsZero() {
h.Set(lmn, tm.UTC().Format(http.TimeFormat))
}
}
// 从 HTTP Header 或 MimeHeader 中获取 Meta
func MetaOfHttpHeader(header http.Header) (meta Meta, err error) {
if len(header) == 0 {
return nil, errors.New("nil or empty header")
}
fn := func(call func(val string) error, keys ...string) error {
for _, key := range keys {
if v := header.Get(key); v != "" {
return call(v)
}
}
return nil
}
m := &ResourceMeta{contentType: header.Get(META_CONTENT_TYPE), contentLength: -1}
if err = fn(func(val string) error {
cl, err := strconv.ParseInt(val, 10, 64)
if err == nil {
m.contentLength = cl
}
return err
}, META_CONTENT_LENGTH); err != nil {
return nil, err
}
if err = fn(func(val string) error {
et, err := ETagParse(val)
if err == nil {
m.etag = et
}
return err
}, META_ETAG, META_REQ_ETAG); err != nil {
return nil, err
}
if err = fn(func(val string) error {
lm, err := http.ParseTime(val)
if err == nil {
m.lastModified = lm
}
return err
}, META_LAST_MODIFIED, META_REQ_MODIFIED); err != nil {
return nil, err
}
return m, nil
}
// 从 reader 中读取 HTTP 头部格式的文本,并解析出 Meta
func MetaOfHttpHeaderTextReader(reader io.Reader) (Meta, error) {
hd, err := textproto.NewReader(bufio.NewReader(reader)).ReadMIMEHeader()
if err != nil && err != io.EOF {
return nil, err
}
return MetaOfHttpHeader(http.Header(hd))
}
// 从指定文件中读取 HTTP 头部格式的文本,并解析出 Meta
func MetaOfHttpHeaderTextFile(fn string) (Meta, error) {
fd, err := os.Open(fn)
if err != nil {
return nil, err
}
defer fd.Close()
return MetaOfHttpHeaderTextReader(fd)
}
// 读取文件,根据文件生成元数据
func MetaOfFile(fn string, etager ETager) (Meta, error) {
fd, err := os.Open(fn)
if err != nil {
return nil, err
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
return nil, err
}
return MetaOfReader(fd, etager, stat.ModTime(), filepath.Ext(fn))
}
// 读取文件,根据文件生成元数据,但忽略 ETag (返回的 Meta 中的 ETag 为 nil)
func MetaOfFileWithoutETag(fn string) (*ResourceMeta, error) {
fd, err := os.Open(fn)
if err != nil {
return nil, err
}
defer fd.Close()
stat, err := fd.Stat()
if err != nil {
return nil, err
}
meta := &ResourceMeta{contentLength: stat.Size(), lastModified: stat.ModTime()}
buf := make([]byte, 512)
io.ReadFull(fd, buf)
meta.contentType = mimeTypeDetect(buf, filepath.Ext(fn))
return meta, nil
}
// 从 reader 读取内容,根据内容及或后缀名(extName,例如 .jpg)生成元数据。
// 注意:MetaOfReader 会从头到尾读取 reader,所以要注意 reader 是否可重复读或一次性读。
func MetaOfReader(reader io.Reader, etager ETager, lastModified time.Time, extName string) (Meta, error) {
return MetaOfCopy(io.Discard, reader, etager, lastModified, extName)
}
// 从 reader 读取内容,根据内容及或后缀名(extName,例如 .jpg)生成元数据。
// 注意:MetaOfReader 会从头到尾读取 reader,所以要注意 reader 是否可重复读或一次性读。
func MetaOfCopy(writer io.Writer, reader io.Reader, etager ETager, lastModified time.Time, extName string) (*ResourceMeta, error) {
// 用于接收前 512 bytes 来推断 mime-type。
mb := new(bytes.Buffer)
// 计算 ETag:
// 1. 将 reader 复制到 writer 以实现存储或输出等目的
// 2. 从 reader 读取前 512 字节,计算 MimeType
// 3. 将 reader 写入到 hash.Hash 计算 Hash 值以生成 ETag
etag, n, err := etager.OfCopy(io.MultiWriter(writer, NewLimitedWriter(mb, 512)), reader)
if err != nil {
return nil, err
}
return &ResourceMeta{
contentType: mimeTypeDetect(mb.Bytes(), extName),
contentLength: n,
lastModified: lastModified,
etag: etag,
}, nil
}
func mimeTypeDetect(buf []byte, ext string) string {
mt := http.DetectContentType(buf)
if (mt == MIME_OCTETE_STREAM || mt == "") && ext != "" {
mt = mime.TypeByExtension(ext)
}
return mt
}
func metaFix(inputed Meta, computed *ResourceMeta) Meta {
if computed == nil {
return inputed
}
if inputed == nil || (inputed.ETag() == nil && inputed.ContentType() == "") {
return computed
}
if ct, rt := inputed.ContentType(), computed.ContentType(); ct != "" && (rt == "" || rt == MIME_OCTETE_STREAM) {
computed.contentType = ct // 计算值 computed 更加准确,所以计算值为空或默认值时才设置
}
if cl, rl := inputed.ContentLength(), computed.ContentLength(); cl > 0 && rl <= 0 {
computed.contentLength = cl // 计算值 computed 的长度更准确,所以计算值非法时才设置
}
if me, re := inputed.ETag(), computed.ETag(); me != nil && re == nil {
computed.etag = me // 计算值 computed 的 ETag 更准确,所以计算值为空时才设置
}
if t := inputed.LastModified(); !t.IsZero() {
computed.lastModified = t // 输入值 inputed 的 LastModified 更准确
}
return computed
}
// 保存资源内容,并通过资源内容,修复 Meta 中缺失的信息。
func MetaFix(w io.Writer, r io.Reader, etager ETager, meta Meta, mt time.Time, ext string) (Meta, error) {
ret, err := MetaOfCopy(w, r, etager, mt, ext)
if err != nil {
return ret, err
}
return metaFix(meta, ret), nil
}
// 将 Meta 转为 HTTP 头部格式的文本
func MetaHttpHeaderText(meta Meta) string {
if meta == nil {
return ""
}
sb := new(strings.Builder)
if ct := meta.ContentType(); ct != "" {
fmt.Fprintf(sb, "%s: %s\r\n", META_CONTENT_TYPE, ct)
}
if size := meta.ContentLength(); size >= 0 {
fmt.Fprintf(sb, "%s: %s\r\n", META_CONTENT_LENGTH, fmt.Sprintf("%d", size))
}
if etag := meta.ETag(); etag != nil {
fmt.Fprintf(sb, "%s: %s\r\n", META_ETAG, etag.String())
}
if tm := meta.LastModified(); !tm.IsZero() {
fmt.Fprintf(sb, "%s: %s\r\n", META_LAST_MODIFIED, tm.UTC().Format(http.TimeFormat))
}
return sb.String()
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/sysadm/resml.git
git@gitee.com:sysadm/resml.git
sysadm
resml
资源管理库
master

搜索帮助