背景
笔者将以 go 语言去实现一个分布式对象存储系统,将最开始的单机版模型慢慢扩展为分布式模型。
参考资料
《分布式对象存储——原理、架构及Go语言实现》,胡世杰 著
架构
在服务器端,我们提供一个 REST 接口,PUT 请求时在服务器本地磁盘创建存储对象,GET 请求时在服务器本地磁盘获取存储对象,服务与数据都在同一台服务器上,没有解耦,仅实现了最简单的存储功能。
业务处理函数
由 PUT 方法创建或者替换一个文件对象,由 GET 方法获取一个文件对象,因此定义一个处理业务的函数用于绑定 URL 。
package objects
import (
"io"
"log"
"net/http"
"os"
"strings"
)
/*
PUT/GET 业务处理函数
*/
func Handler(w http.ResponseWriter, r *http.Request) {
m := r.Method
// PUT 方法时,创建或者替换资源
if m == http.MethodPut {
log.Println("PUT 方法")
put(w, r)
return
}
// GET 方法时,获取资源
if m == http.MethodGet {
log.Println("GET 方法")
get(w, r)
return
}
// 其他方式时,返回状态码,方法不允许
w.WriteHeader(http.StatusMethodNotAllowed)
}
func put(w http.ResponseWriter, r *http.Request) {
// 处理文件路径,将文件保存在 D:/uploadFile 目录下,文件名为客户端 PUT 请求时的名字,文件名应为转义后的名字
// 文件路径可以使用 os.Getenv(var) 通过环境变量来指定会更加灵活
log.Println(r.URL.EscapedPath())
fileName := "D:/uploadFiles/" + strings.Split(r.URL.EscapedPath(), "/")[2]
// 创建文件
f, e := os.Create(fileName)
if e != nil {
log.Println(e)
// 返回服务器错误状态码
w.WriteHeader(http.StatusInternalServerError)
return
}
// 将客户端 PUT 请求的文件数据保存到服务端创建的文件 f 中
io.Copy(f, r.Body)
// 关闭文件
defer f.Close()
}
func get(w http.ResponseWriter, r *http.Request) {
// 处理文件路径,从 D:/uploadFile 目录下获取文件,文件名为客户端 GET 请求时的名字,文件名应为转义后的名字
// 文件路径可以使用 os.Getenv(var) 通过环境变量来指定会更加灵活
log.Println(r.URL.EscapedPath())
fileName := "D:/uploadFiles/" + strings.Split(r.URL.EscapedPath(), "/")[2]
// 创建文件
f, e := os.Open(fileName)
if e != nil {
log.Println(e)
// 返回文件未找到状态码
w.WriteHeader(http.StatusNotFound)
return
}
// 将服务端的文件 f 复制到响应体 w 中
io.Copy(w, f)
// 关闭文件
defer f.Close()
}
/*
注意:
f 本身类型是 *os.File,它是一个指向 os.File 结构体的指针,而 os.File 结构体同时实现了 io.Writer 和 io.Reader 两个接口,
因此它既是一个 io.Writer 又是 一个 io.Reader,可以在 io.Copy 中作为不同的参数使用。
*/
main 入口
将业务路由 URL 与业务处理函数绑定,启动服务。
package main
import (
"demo/objects"
"net/http"
)
func main() {
http.HandleFunc("/handleObjs/", objects.Handler)
// 使用 os.Getenv(var) 更加灵活
http.ListenAndServe(":8080", nil)
}
测试
客户端 PUT 请求
文件创建成功。
客户端 GET 请求
获取文件成功。
使用 POST 方法
方法错误。
获取不存在的文件
文件未找到。