funcHandler(w http.ResponseWriter, r *http.Request) { m := r.Method if m == http.MethodHead { head(w, r) return } if m == http.MethodPut { put(w, r) return } w.WriteHeader(http.StatusMethodNotAllowed) }
temp.Handler函数首先检查访问方式,如果是HEAD则调用head函数,如果是PUT 则调用put函数,否则返回405 Method Not Allowed.。put相关函数见例6-6。 例6-6
funcput(w http.ResponseWriter, r *http.Request) { token := strings.Split(r.URL.EscapedPath(), "/")[2] stream, e := rs.NewRSResumablePutStreamFromToken(token) if e != nil { log.Println(e) w.WriteHeader(http.StatusForbidden) return } current := stream.CurrentSize() if current == -1 { w.WriteHeader(http.StatusNotFound) return } offset := utils.GetOffsetFromHeader(r.Header) if current != offset { w.WriteHeader(http.StatusRequestedRangeNotSatisfiable) return } bytes := make([]byte, rs.BLOCK_SIZE) for { n, e := io.ReadFull(r.Body, bytes) if e != nil && e != io.EOF && e != io.ErrUnexpectedEOF { log.Println(e) w.WriteHeader(http.StatusInternalServerError) return } current += int64(n) if current > stream.Size { stream.Commit(false) log.Println("resumable put exceed size") w.WriteHeader(http.StatusForbidden) return } if n != rs.BLOCK_SIZE && current != stream.Size { return } stream.Write(bytes[:n]) if current == stream.Size { stream.Flush() getStream, e := rs.NewRSResumableGetStream(stream.Servers, stream.Uuids, stream.Size) hash := url.PathEscape(utils.CalculateHash(getStream)) if hash != stream.Hash { stream.Commit(false) log.Println("resumable put done but hash mismatch") w.WriteHeader(http.StatusForbidden) return } if locate.Exist(url.PathEscape(hash)) { stream.Commit(false) } else { stream.Commit(true) } e = es.AddVersion(stream.Name, stream.Hash, stream.Size) if e != nil { log.Println(e) w.WriteHeader(http.StatusInternalServerError) } return } } }
funcNewRSResumablePutStream(dataServers []string, name, hash string, size int64) (*RSResumablePutStream, error) { putStream, e := NewRSPutStream(dataServers, hash, size) if e != nil { returnnil, e } uuids := make([]string, ALL_SHARDS) for i := range uuids { uuids[i] = putStream.writers[i].(*objectstream.TempPutStream).Uuid } token := &resumableToken{name, size, hash, dataServers, uuids} return &RSResumablePutStream{putStream, token}, nil }
func(s *RSResumablePutStream) CurrentSize() int64 { r, e := http.Head(fmt.Sprintf("http://%s/temp/%s", s.Servers[0], s.Uuids[0])) if e != nil { log.Println(e) return-1 } if r.StatusCode != http.StatusOK { log.Println(r.StatusCode) return-1 } size := utils.GetSizeFromHeader(r.Header) * DATA_SHARDS if size > s.Size { size = s.Size } return size }
put函数首先从URL中获取<token>,然后调用rs.NewRSResumablePutStreamFromToken根据<token>中的内容创建RSResumablePutStream结构体并获得指向该结构体的指针stream,然后调用CurrentSize方法获得token当前大小,如果大小为-1,则说明该token不存在。接下来我们调用utils.GetOffsetFromHeader从Range头部获得offset。如果offset和当前的大小不一致,则接口服务返回416 Range Not Satisfiable。
funcHandler(w http.ResponseWriter, r *http.Request) { m := r.Method if m == http.MethodHead { head(w, r) return } if m == http.MethodGet { get(w, r) return } if m == http.MethodPut { put(w, r) return } if m == http.MethodPatch { patch(w, r) return } if m == http.MethodPost { post(w, r) return } if m == http.MethodDelete { del(w, r) return } w.WriteHeader(http.StatusMethodNotAllowed) }
funcget(w http.ResponseWriter, r *http.Request) { uuid := strings.Split(r.URL.EscapedPath(), "/")[2] f, e := os.Open(os.Getenv("STORAGE_ROOT") + "/temp/" + uuid + ".dat") if e != nil { log.Println(e) w.WriteHeader(http.StatusNotFound) return } defer f.Close() io.Copy(w, f) }
funchead(w http.ResponseWriter, r *http.Request) { uuid := strings.Split(r.URL.EscapedPath(), "/")[2] f, e := os.Open(os.Getenv("STORAGE_ROOT") + "/temp/" + uuid + ".dat") if e != nil { log.Println(e) w.WriteHeader(http.StatusNotFound) return } defer f.Close() info, e := f.Stat() if e != nil { log.Println(e) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("content-length", fmt.Sprintf("%d", info.Size())) }
➜ ddif=/tmp/file of=/tmp/second bs=1000 skip=32 count=68 68+0 records in 68+0 records out 68000 bytes(68kB)copied,0.000775909s,87.6MB/s ➜ curl -v -XPUT --data-binary @/tmp/second -H "range:bytes=32000-"10.29.2.1: 12345/temp/eyJOYW11IjoidGVzdDYiLCJTaXplIjoxMDAwMDAsIkhhc2gioiJtWE5YdjZ yWTdrK2pDNmpLVDRMRmhWTDVPTnNsaytyU0dMbotiU2VFNW5jPSIsIlN1cnZ1cnMiolsiM TAuMjkuMs410jEyMzQ1IiwiMTAuMjkuMs4yojEyMzQ1IiwiMTAuMjkuMS4xojEyMzQ1Iiw iMTAuMjkuMs400jEyMzQ1IiwiMTAuMjkuMs4zojEyMzQlIiwiMTAuMj kuMS420jEyMzQ1I 10sI1VlaWRzIjpbIjhjZTI2ZDBkLTJhN2MtNGVizi1hNGJjLTgwYmU4YTZjY2VkMiIsIjJ mNDE2YTcyLTN1ZDAtNGQwZS1hMjAyLTNjZDEzMjAxYzIzNyIsIj1kNjQ3Y2JhLWE0ZmItN DAIMC1iNWNhLTZhYjE3YjgxOGE4MiIs ImF1NmNhMWYOLTQ4MzQtNGMyMCO5MDk2LTIZZTI 5YTQ3MTE5ZiIsImUONjU4ZmY3LTQXNDMtNDF1ZS1iYWJmLTQwYmU4MZNIMTk1NCIsIjdiY mYONzdkLWQxNjgtNDMxYiliN2M2LTU3NDgwZWEwY2UyNyJdfQ== Hostname was NOT found in DNS cache *Trying10.29.2.1.. *Connected to10.29.2.1(10.29.2.1)port12345(t0) PUT /temp/eyJOYW11IjoidGVzdDYiLCJTaXplIjoxMDAwMDAsIkhhc2gioiJtWE5 YdjZyWTdrK2pDNmpLVDRMRmhWTDVPTnNsaytyuodMbotiU2VENW5jPSIsI1N1cnz1cnMiol siMTAuMj kuMS410jEyMzQ1IiwiMTAuMj kuMs4yojEyMzQ1IiwiMTAuMj kuMS4xojEyMzQ1 IiwiMTAuMjkuMS400jEyMzQ1IiwiMTAuMj kuMS4zojEyMzQ1IiwiMTAuMjkuMS420jEyMz Q1I10sIlV1aWRzIjpbIjhjZTI2ZDBkLTJhN2MtNGVizilhNGJjLTgwYmU4YTZjY2VkMiIs IjJmNDE2YTeyLTNIZDAtNGQwZS1hMjAyLTNjZDEzMjAxYzIzNyIsIjlkNjQ3Y2JhLWEOZm ItNDA1MC1iNWNhLTZhYjE3YjgxOGE4MiIsImF1NmNhMWYOLTQ4MzQtNGMyMC05MDk2LTIz ZTI5YTQ3MTE5ZiIsImUONjU4ZmY3LTQXNDMtNDF12S1iYWJmLTQwYmU4MzNIMTkINCIsIj diYmYONzdkLWQxNjgtNDMxYiliN2M2LTU3NDgwZWEwY2UyNyJdfQ==HTTP/1.1 User-Agent:curl/7.38.0 >Host:10.29.2.1:12345 Accept:*/ range:bytes=32000- Content-Length:68000 Content-Type:application/x-www-form-urlencoded Expect:100-continue > HTTP/1.1 100 Continue HTTP/1.1 200 OK Date:Tue,15 Aug 2017 17:28:31 GMT Content-Length:0 Content-Type:text/plain;charset=utf-8 Connection #0 to host 10.29.2.1 left intact
现在让我们GET这个对象对比一下数据。
1 2 3 4 5 6
➜ curl 10.29.2.1:12345/objects/test6 /tmp/output $Total Received Xferd Average Speed TimeTime Time Current Dload Upload Total Spent Left Speed 100 97k 0 97k 0 0 2922k 0--:--:----:--:----:--:--2959k ➜ diff -s /tmp/output /tmp/file Files /tmp/output and /tmp/file are identical
接下来让我们试试用range头部指定下载test6对象的后68KB数据。
1 2 3 4 5 6
➜ curl 10.29.2.1:12345/objects/test6-H "range:bytes=32000-">/tmp/output2 % Total % Received % Xferd Average Speed Time TimeTime Current Dload Upload Total Spent Left Speed 10068000.068000002084k0--:--:----:--:----:--:--2075k ➜ diff -s /tmp/output2 /tmp/second Files /tmp/output2 and /tmp/second are identical