Repository: dragonflyoss/Dragonfly2
Pull request #1402 (2022-06-20 12:25:22)

refactor: pkg util
Signed-off-by: Gaius <gaius.qi@gmail.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description
- Refactor pkg util.
<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.



Patch:
diff --git a/client/config/dfcache.go b/client/config/dfcache.go
index 34c7e13f..675dca36 100644
--- a/client/config/dfcache.go
+++ b/client/config/dfcache.go
@@ -31,7 +31,7 @@ import (
 	"d7y.io/dragonfly/v2/cmd/dependency/base"
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	"d7y.io/dragonfly/v2/pkg/basic"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"d7y.io/dragonfly/v2/pkg/strings"
 )
 
 type DfcacheConfig = CacheOption
@@ -106,7 +106,7 @@ func (cfg *CacheOption) Validate(cmd string) error {
 	if cfg.Cid == "" {
 		return errors.Wrap(dferrors.ErrInvalidArgument, "missing Cid")
 	}
-	if stringutils.IsBlank(cfg.Cid) {
+	if strings.IsBlank(cfg.Cid) {
 		return errors.Wrap(dferrors.ErrInvalidArgument, "Cid are all blanks")
 	}
 
@@ -225,7 +225,7 @@ func (cfg *CacheOption) checkOutput() error {
 	}
 
 	// check permission
-	for dir := cfg.Output; !stringutils.IsBlank(dir); dir = filepath.Dir(dir) {
+	for dir := cfg.Output; !strings.IsBlank(dir); dir = filepath.Dir(dir) {
 		if err := syscall.Access(dir, syscall.O_RDWR); err == nil {
 			break
 		} else if os.IsPermission(err) || dir == "/" {
diff --git a/client/config/dfget.go b/client/config/dfget.go
index 22fd1088..55cb4d84 100644
--- a/client/config/dfget.go
+++ b/client/config/dfget.go
@@ -35,9 +35,9 @@ import (
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/basic"
+	"d7y.io/dragonfly/v2/pkg/net/url"
+	pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
 	"d7y.io/dragonfly/v2/pkg/unit"
-	"d7y.io/dragonfly/v2/pkg/util/net/urlutils"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 )
 
 type DfgetConfig = ClientOption
@@ -143,7 +143,7 @@ func (cfg *ClientOption) Validate() error {
 		return errors.Wrap(dferrors.ErrInvalidArgument, "runtime config")
 	}
 
-	if !urlutils.IsValidURL(cfg.URL) {
+	if !url.IsValid(cfg.URL) {
 		return errors.Wrapf(dferrors.ErrInvalidArgument, "url: %v", cfg.URL)
 	}
 
@@ -171,7 +171,7 @@ func (cfg *ClientOption) Validate() error {
 }
 
 func (cfg *ClientOption) Convert(args []string) error {
-	if stringutils.IsBlank(cfg.Output) {
+	if pkgstrings.IsBlank(cfg.Output) {
 		url := strings.TrimRight(cfg.URL, "/")
 		idx := strings.LastIndexByte(url, '/')
 		if idx < 0 {
@@ -249,7 +249,7 @@ func (cfg *ClientOption) checkOutput() error {
 	}
 
 	// check permission
-	for dir := cfg.Output; !stringutils.IsBlank(dir); dir = filepath.Dir(dir) {
+	for dir := cfg.Output; !pkgstrings.IsBlank(dir); dir = filepath.Dir(dir) {
 		if err := syscall.Access(dir, syscall.O_RDWR); err == nil {
 			break
 		} else if os.IsPermission(err) || dir == "/" {
diff --git a/client/config/peerhost.go b/client/config/peerhost.go
index e17bbeee..38e75234 100644
--- a/client/config/peerhost.go
+++ b/client/config/peerhost.go
@@ -37,9 +37,9 @@ import (
 	"d7y.io/dragonfly/v2/cmd/dependency/base"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
+	netip "d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	"d7y.io/dragonfly/v2/pkg/unit"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
 type DaemonConfig = DaemonOption
@@ -102,7 +102,7 @@ func (p *DaemonOption) Convert() error {
 	// AdvertiseIP
 	ip := net.ParseIP(p.Host.AdvertiseIP)
 	if ip == nil || net.IPv4zero.Equal(ip) {
-		p.Host.AdvertiseIP = iputils.IPv4
+		p.Host.AdvertiseIP = netip.IPv4
 	} else {
 		p.Host.AdvertiseIP = ip.String()
 	}
diff --git a/client/config/peerhost_darwin.go b/client/config/peerhost_darwin.go
index 6eeb4aaf..16988553 100644
--- a/client/config/peerhost_darwin.go
+++ b/client/config/peerhost_darwin.go
@@ -28,8 +28,8 @@ import (
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
-	"d7y.io/dragonfly/v2/pkg/util/hostutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
+	"d7y.io/dragonfly/v2/pkg/net/fqdn"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 )
 
 var peerHostConfig = DaemonOption{
@@ -58,9 +58,9 @@ var peerHostConfig = DaemonOption{
 		ScheduleTimeout: clientutil.Duration{Duration: DefaultScheduleTimeout},
 	},
 	Host: HostOption{
-		Hostname:       hostutils.Hostname,
+		Hostname:       fqdn.FQDNHostname,
 		ListenIP:       net.IPv4zero.String(),
-		AdvertiseIP:    iputils.IPv4,
+		AdvertiseIP:    ip.IPv4,
 		SecurityDomain: "",
 		Location:       "",
 		IDC:            "",
diff --git a/client/config/peerhost_linux.go b/client/config/peerhost_linux.go
index 54da7c47..dd818070 100644
--- a/client/config/peerhost_linux.go
+++ b/client/config/peerhost_linux.go
@@ -28,8 +28,8 @@ import (
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
-	"d7y.io/dragonfly/v2/pkg/util/hostutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
+	"d7y.io/dragonfly/v2/pkg/net/fqdn"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 )
 
 var peerHostConfig = DaemonOption{
@@ -58,9 +58,9 @@ var peerHostConfig = DaemonOption{
 		ScheduleTimeout: clientutil.Duration{Duration: DefaultScheduleTimeout},
 	},
 	Host: HostOption{
-		Hostname:       hostutils.Hostname,
+		Hostname:       fqdn.FQDNHostname,
 		ListenIP:       "0.0.0.0",
-		AdvertiseIP:    iputils.IPv4,
+		AdvertiseIP:    ip.IPv4,
 		SecurityDomain: "",
 		Location:       "",
 		IDC:            "",
diff --git a/client/daemon/peer/peertask_conductor.go b/client/daemon/peer/peertask_conductor.go
index cc60f3a6..f515e9b2 100644
--- a/client/daemon/peer/peertask_conductor.go
+++ b/client/daemon/peer/peertask_conductor.go
@@ -37,12 +37,12 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/storage"
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/idgen"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	schedulerclient "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 const (
@@ -810,7 +810,7 @@ func (pt *peerTaskConductor) pullSinglePiece() {
 
 	pt.SetContentLength(int64(pt.singlePiece.PieceInfo.RangeSize))
 	pt.SetTotalPieces(1)
-	pt.SetPieceMd5Sign(digestutils.Sha256(pt.singlePiece.PieceInfo.PieceMd5))
+	pt.SetPieceMd5Sign(digest.Sha256(pt.singlePiece.PieceInfo.PieceMd5))
 
 	request := &DownloadPieceRequest{
 		storage: pt.GetStorage(),
diff --git a/client/daemon/peer/piece_downloader.go b/client/daemon/peer/piece_downloader.go
index 813264a5..40111e68 100644
--- a/client/daemon/peer/piece_downloader.go
+++ b/client/daemon/peer/piece_downloader.go
@@ -27,9 +27,9 @@ import (
 
 	"d7y.io/dragonfly/v2/client/daemon/storage"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 type DownloadPieceRequest struct {
@@ -178,7 +178,7 @@ func (p *pieceDownloader) DownloadPiece(ctx context.Context, req *DownloadPieceR
 	reader, closer := resp.Body.(io.Reader), resp.Body.(io.Closer)
 	if req.CalcDigest {
 		req.log.Debugf("calculate digest for piece %d, digest: %s", req.piece.PieceNum, req.piece.PieceMd5)
-		reader, err = digestutils.NewDigestReader(req.log, io.LimitReader(resp.Body, int64(req.piece.RangeSize)), req.piece.PieceMd5)
+		reader, err = digest.NewReader(req.log, io.LimitReader(resp.Body, int64(req.piece.RangeSize)), req.piece.PieceMd5)
 		if err != nil {
 			_ = closer.Close()
 			req.log.Errorf("init digest reader error: %s", err.Error())
diff --git a/client/daemon/peer/piece_manager.go b/client/daemon/peer/piece_manager.go
index 50b69fb4..954f8615 100644
--- a/client/daemon/peer/piece_manager.go
+++ b/client/daemon/peer/piece_manager.go
@@ -34,11 +34,11 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/storage"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/util"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 type PieceManager interface {
@@ -205,7 +205,7 @@ func (pm *pieceManager) processPieceFromSource(pt Task,
 	}
 	if pm.calculateDigest {
 		pt.Log().Debugf("calculate digest")
-		reader, _ = digestutils.NewDigestReader(pt.Log(), reader)
+		reader, _ = digest.NewReader(pt.Log(), reader)
 	}
 	var n int64
 	result.Size, err = pt.GetStorage().WritePiece(
@@ -218,7 +218,7 @@ func (pm *pieceManager) processPieceFromSource(pt Task,
 			},
 			PieceMetadata: storage.PieceMetadata{
 				Num: pieceNum,
-				// storage manager will get digest from DigestReader, keep empty here is ok
+				// storage manager will get digest from Reader, keep empty here is ok
 				Md5:    "",
 				Offset: pieceOffset,
 				Range: clientutil.Range{
@@ -239,7 +239,7 @@ func (pm *pieceManager) processPieceFromSource(pt Task,
 		return
 	}
 	if pm.calculateDigest {
-		md5 = reader.(digestutils.DigestReader).Digest()
+		md5 = reader.(digest.Reader).Digest()
 	}
 	return
 }
@@ -296,7 +296,7 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, request *sc
 
 	// calc total
 	if pm.calculateDigest {
-		reader, err = digestutils.NewDigestReader(pt.Log(), response.Body, request.UrlMeta.Digest)
+		reader, err = digest.NewReader(pt.Log(), response.Body, request.UrlMeta.Digest)
 		if err != nil {
 			log.Errorf("init digest reader error: %s", err.Error())
 			return err
@@ -462,7 +462,7 @@ func (pm *pieceManager) processPieceFromFile(ctx context.Context, ptm storage.Pe
 
 	if pm.calculateDigest {
 		log.Debugf("calculate digest in processPieceFromFile")
-		reader, _ = digestutils.NewDigestReader(log, r)
+		reader, _ = digest.NewReader(log, r)
 	}
 	n, err := tsd.WritePiece(ctx,
 		&storage.WritePieceRequest{
@@ -470,7 +470,7 @@ func (pm *pieceManager) processPieceFromFile(ctx context.Context, ptm storage.Pe
 			PeerTaskMetadata: ptm,
 			PieceMetadata: storage.PieceMetadata{
 				Num: pieceNum,
-				// storage manager will get digest from DigestReader, keep empty here is ok
+				// storage manager will get digest from Reader, keep empty here is ok
 				Md5:    "",
 				Offset: pieceOffset,
 				Range: clientutil.Range{
diff --git a/client/daemon/proxy/proxy.go b/client/daemon/proxy/proxy.go
index b695328d..bf79879a 100644
--- a/client/daemon/proxy/proxy.go
+++ b/client/daemon/proxy/proxy.go
@@ -44,7 +44,7 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/transport"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
 )
 
 var (
@@ -564,7 +564,7 @@ func (proxy *Proxy) checkWhiteList(r *http.Request) bool {
 			}
 
 			// Hit ports
-			if stringutils.Contains(v.Ports, port) {
+			if pkgstrings.Contains(v.Ports, port) {
 				return true
 			}
 
diff --git a/client/daemon/rpcserver/rpcserver.go b/client/daemon/rpcserver/rpcserver.go
index 647ab870..ba08cd0b 100644
--- a/client/daemon/rpcserver/rpcserver.go
+++ b/client/daemon/rpcserver/rpcserver.go
@@ -40,13 +40,13 @@ import (
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/idgen"
+	"d7y.io/dragonfly/v2/pkg/net/http"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
 	dfdaemongrpc "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
 	dfdaemonserver "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	"d7y.io/dragonfly/v2/pkg/safe"
-	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 	"d7y.io/dragonfly/v2/scheduler/resource"
 )
 
@@ -334,7 +334,7 @@ func (s *server) doDownload(ctx context.Context, req *dfdaemongrpc.DownRequest,
 		KeepOriginalOffset: req.KeepOriginalOffset,
 	}
 	if len(req.UrlMeta.Range) > 0 {
-		r, err := rangeutils.ParseRange(req.UrlMeta.Range, math.MaxInt)
+		r, err := http.ParseRange(req.UrlMeta.Range, math.MaxInt)
 		if err != nil {
 			err = fmt.Errorf("parse range %s error: %s", req.UrlMeta.Range, err)
 			return err
diff --git a/client/daemon/rpcserver/seeder.go b/client/daemon/rpcserver/seeder.go
index 5c3ae162..9ad92b91 100644
--- a/client/daemon/rpcserver/seeder.go
+++ b/client/daemon/rpcserver/seeder.go
@@ -31,11 +31,11 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/peer"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/idgen"
+	"d7y.io/dragonfly/v2/pkg/net/http"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/base/common"
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
-	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 )
 
 type seeder struct {
@@ -77,7 +77,7 @@ func (s *seeder) ObtainSeeds(seedRequest *cdnsystem.SeedRequest, seedsServer cdn
 	log := logger.With("peer", req.PeerId, "task", seedRequest.TaskId, "component", "seedService")
 
 	if len(req.UrlMeta.Range) > 0 {
-		r, err := rangeutils.ParseRange(req.UrlMeta.Range, math.MaxInt)
+		r, err := http.ParseRange(req.UrlMeta.Range, math.MaxInt)
 		if err != nil {
 			metrics.SeedPeerDownloadFailureCount.Add(1)
 			err = fmt.Errorf("parse range %s error: %s", req.UrlMeta.Range, err)
diff --git a/client/daemon/storage/local_storage.go b/client/daemon/storage/local_storage.go
index 440c1ef4..6918e384 100644
--- a/client/daemon/storage/local_storage.go
+++ b/client/daemon/storage/local_storage.go
@@ -33,8 +33,8 @@ import (
 	"d7y.io/dragonfly/v2/client/clientutil"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/util"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 type localTaskStore struct {
@@ -155,11 +155,11 @@ func (t *localTaskStore) WritePiece(ctx context.Context, req *WritePieceRequest)
 	// when Md5 is empty, try to get md5 from reader, it's useful for back source
 	if req.PieceMetadata.Md5 == "" {
 		t.Debugf("piece md5 not found in metadata, read from reader")
-		if get, ok := req.Reader.(digestutils.DigestReader); ok {
+		if get, ok := req.Reader.(digest.Reader); ok {
 			req.PieceMetadata.Md5 = get.Digest()
 			t.Infof("read md5 from reader, value: %s", req.PieceMetadata.Md5)
 		} else {
-			t.Debugf("reader is not a DigestReader")
+			t.Debugf("reader is not a digest.Reader")
 		}
 	}
 
@@ -195,7 +195,7 @@ func (t *localTaskStore) genMetadata(n int64, req *WritePieceRequest) {
 		pieceDigests = append(pieceDigests, t.Pieces[i].Md5)
 	}
 
-	digest := digestutils.Sha256(pieceDigests...)
+	digest := digest.Sha256(pieceDigests...)
 	t.PieceMd5Sign = digest
 	t.Infof("generated digest: %s, total pieces: %d, content length: %d", digest, t.TotalPieces, t.ContentLength)
 }
@@ -241,7 +241,7 @@ func (t *localTaskStore) ValidateDigest(*PeerTaskMetadata) error {
 		pieceDigests = append(pieceDigests, t.Pieces[i].Md5)
 	}
 
-	digest := digestutils.Sha256(pieceDigests...)
+	digest := digest.Sha256(pieceDigests...)
 	if digest != t.PieceMd5Sign {
 		t.Errorf("invalid digest, desired: %s, actual: %s", t.PieceMd5Sign, digest)
 		t.invalid.Store(true)
diff --git a/client/daemon/storage/local_storage_subtask.go b/client/daemon/storage/local_storage_subtask.go
index 266cf12a..42696658 100644
--- a/client/daemon/storage/local_storage_subtask.go
+++ b/client/daemon/storage/local_storage_subtask.go
@@ -26,8 +26,8 @@ import (
 
 	"d7y.io/dragonfly/v2/client/clientutil"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 // TODO need refactor with localTaskStore, currently, localSubTaskStore code copies from localTaskStore
@@ -103,11 +103,11 @@ func (t *localSubTaskStore) WritePiece(ctx context.Context, req *WritePieceReque
 	// when Md5 is empty, try to get md5 from reader, it's useful for back source
 	if req.PieceMetadata.Md5 == "" {
 		t.Debugf("piece md5 not found in metadata, read from reader")
-		if get, ok := req.Reader.(digestutils.DigestReader); ok {
+		if get, ok := req.Reader.(digest.Reader); ok {
 			req.PieceMetadata.Md5 = get.Digest()
 			t.Infof("read md5 from reader, value: %s", req.PieceMetadata.Md5)
 		} else {
-			t.Debugf("reader is not a DigestReader")
+			t.Debugf("reader is not a digest.Reader")
 		}
 	}
 
@@ -328,7 +328,7 @@ func (t *localSubTaskStore) ValidateDigest(req *PeerTaskMetadata) error {
 		pieceDigests = append(pieceDigests, t.Pieces[i].Md5)
 	}
 
-	digest := digestutils.Sha256(pieceDigests...)
+	digest := digest.Sha256(pieceDigests...)
 	if digest != t.PieceMd5Sign {
 		t.Errorf("invalid digest, desired: %s, actual: %s", t.PieceMd5Sign, digest)
 		t.invalid.Store(true)
@@ -359,7 +359,7 @@ func (t *localSubTaskStore) genMetadata(n int64, req *WritePieceRequest) {
 		pieceDigests = append(pieceDigests, t.Pieces[i].Md5)
 	}
 
-	digest := digestutils.Sha256(pieceDigests...)
+	digest := digest.Sha256(pieceDigests...)
 	t.PieceMd5Sign = digest
 	t.Infof("generated digest: %s, total pieces: %d, content length: %d", digest, t.TotalPieces, t.ContentLength)
 }
diff --git a/client/daemon/transport/transport.go b/client/daemon/transport/transport.go
index 3dbf292b..2e02d1ff 100644
--- a/client/daemon/transport/transport.go
+++ b/client/daemon/transport/transport.go
@@ -39,9 +39,9 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/metrics"
 	"d7y.io/dragonfly/v2/client/daemon/peer"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	nethttp "d7y.io/dragonfly/v2/pkg/net/http"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
-	"d7y.io/dragonfly/v2/pkg/util/net/httputils"
 )
 
 var _ *logger.SugaredLoggerOnWith // pin this package for no log code generation
@@ -235,13 +235,13 @@ func (rt *transport) download(ctx context.Context, req *http.Request) (*http.Res
 	}
 
 	// Pick header's parameters
-	filter := httputils.PickHeader(req.Header, config.HeaderDragonflyFilter, rt.defaultFilter)
-	tag := httputils.PickHeader(req.Header, config.HeaderDragonflyBiz, rt.defaultBiz)
+	filter := nethttp.PickHeader(req.Header, config.HeaderDragonflyFilter, rt.defaultFilter)
+	tag := nethttp.PickHeader(req.Header, config.HeaderDragonflyBiz, rt.defaultBiz)
 
 	// Delete hop-by-hop headers
 	delHopHeaders(req.Header)
 
-	meta.Header = httputils.HeaderToMap(req.Header)
+	meta.Header = nethttp.HeaderToMap(req.Header)
 	meta.Tag = tag
 	meta.Filter = filter
 
@@ -264,7 +264,7 @@ func (rt *transport) download(ctx context.Context, req *http.Request) (*http.Res
 		return nil, err
 	}
 
-	hdr := httputils.MapToHeader(attr)
+	hdr := nethttp.MapToHeader(attr)
 	log.Infof("download stream attribute: %v", hdr)
 
 	var contentLength int64 = -1
diff --git a/client/dfget/dfget.go b/client/dfget/dfget.go
index ddc4713d..e661811d 100644
--- a/client/dfget/dfget.go
+++ b/client/dfget/dfget.go
@@ -35,12 +35,12 @@ import (
 	"d7y.io/dragonfly/v2/client/config"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/basic"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
 	daemonclient "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
 )
 
 func Download(cfg *config.DfgetConfig, client daemonclient.DaemonClient) error {
@@ -173,9 +173,9 @@ func downloadFromSource(ctx context.Context, cfg *config.DfgetConfig, hdr map[st
 		return err
 	}
 
-	if !stringutils.IsBlank(cfg.Digest) {
-		parsedHash := digestutils.Parse(cfg.Digest)
-		realHash := digestutils.HashFile(target.Name(), digestutils.Algorithms[parsedHash[0]])
+	if !pkgstrings.IsBlank(cfg.Digest) {
+		parsedHash := digest.Parse(cfg.Digest)
+		realHash := digest.HashFile(target.Name(), digest.Algorithms[parsedHash[0]])
 
 		if realHash != "" && realHash != parsedHash[1] {
 			return errors.Errorf("%s digest is not matched: real[%s] expected[%s]", parsedHash[0], realHash, parsedHash[1])
diff --git a/cmd/dependency/dependency.go b/cmd/dependency/dependency.go
index 526d6b03..59031a4f 100644
--- a/cmd/dependency/dependency.go
+++ b/cmd/dependency/dependency.go
@@ -48,9 +48,9 @@ import (
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
 	"d7y.io/dragonfly/v2/pkg/dfpath"
+	"d7y.io/dragonfly/v2/pkg/net/fqdn"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/pkg/unit"
-	"d7y.io/dragonfly/v2/pkg/util/hostutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 	"d7y.io/dragonfly/v2/version"
 )
 
@@ -229,7 +229,7 @@ func initJaegerTracer(otelOption base.TelemetryOption) (func(), error) {
 		sdktrace.WithResource(resource.NewWithAttributes(
 			semconv.SchemaURL,
 			semconv.ServiceNameKey.String(otelOption.ServiceName),
-			semconv.ServiceInstanceIDKey.String(fmt.Sprintf("%s|%s", hostutils.FQDNHostname, iputils.IPv4)),
+			semconv.ServiceInstanceIDKey.String(fmt.Sprintf("%s|%s", fqdn.FQDNHostname, ip.IPv4)),
 			semconv.ServiceNamespaceKey.String("dragonfly"),
 			semconv.ServiceVersionKey.String(version.GitVersion))),
 	)
diff --git a/cmd/dfget/cmd/root.go b/cmd/dfget/cmd/root.go
index 45d8b57b..ec518564 100644
--- a/cmd/dfget/cmd/root.go
+++ b/cmd/dfget/cmd/root.go
@@ -39,10 +39,10 @@ import (
 	"d7y.io/dragonfly/v2/pkg/basic"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
 	"d7y.io/dragonfly/v2/pkg/dfpath"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/unit"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 	"d7y.io/dragonfly/v2/version"
 )
 
@@ -95,7 +95,7 @@ var rootCmd = &cobra.Command{
 
 		fmt.Printf("--%s--  %s\n", start.Format("2006-01-02 15:04:05"), dfgetConfig.URL)
 		fmt.Printf("dfget version: %s\n", version.GitVersion)
-		fmt.Printf("current user: %s, default peer ip: %s\n", basic.Username, iputils.IPv4)
+		fmt.Printf("current user: %s, default peer ip: %s\n", basic.Username, ip.IPv4)
 		fmt.Printf("output path: %s\n", dfgetConfig.Output)
 
 		//  do get file
diff --git a/manager/job/preheat.go b/manager/job/preheat.go
index ef1753e3..74dccca8 100644
--- a/manager/job/preheat.go
+++ b/manager/job/preheat.go
@@ -39,7 +39,7 @@ import (
 	"d7y.io/dragonfly/v2/manager/config"
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/types"
-	"d7y.io/dragonfly/v2/pkg/util/net/httputils"
+	nethttp "d7y.io/dragonfly/v2/pkg/net/http"
 )
 
 var tracer = otel.Tracer("manager")
@@ -107,7 +107,7 @@ func (p *preheat) CreatePreheat(ctx context.Context, schedulers []model.Schedule
 			return nil, err
 		}
 
-		files, err = p.getLayers(ctx, url, tag, filter, httputils.MapToHeader(rawheader), image)
+		files, err = p.getLayers(ctx, url, tag, filter, nethttp.MapToHeader(rawheader), image)
 		if err != nil {
 			return nil, err
 		}
@@ -250,7 +250,7 @@ func (p *preheat) parseLayers(resp *http.Response, url, tag, filter string, head
 			URL:     layerURL(image.protocol, image.domain, image.name, digest),
 			Tag:     tag,
 			Filter:  filter,
-			Headers: httputils.HeaderToMap(header),
+			Headers: nethttp.HeaderToMap(header),
 		}
 
 		layers = append(layers, layer)
diff --git a/manager/permission/rbac/rbac.go b/manager/permission/rbac/rbac.go
index 8e126e68..6cafab43 100644
--- a/manager/permission/rbac/rbac.go
+++ b/manager/permission/rbac/rbac.go
@@ -30,7 +30,7 @@ import (
 	"gorm.io/gorm"
 
 	managermodel "d7y.io/dragonfly/v2/manager/model"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"d7y.io/dragonfly/v2/pkg/strings"
 )
 
 // Syntax for models see https://casbin.org/docs/en/syntax-for-models
@@ -151,7 +151,7 @@ func GetAPIGroupNames(g *gin.Engine) []string {
 			continue
 		}
 
-		if !stringutils.Contains(apiGroupNames, name) {
+		if !strings.Contains(apiGroupNames, name) {
 			apiGroupNames = append(apiGroupNames, name)
 		}
 	}
diff --git a/manager/searcher/searcher.go b/manager/searcher/searcher.go
index 10ba9753..54078243 100644
--- a/manager/searcher/searcher.go
+++ b/manager/searcher/searcher.go
@@ -27,8 +27,8 @@ import (
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/pkg/math"
 	"d7y.io/dragonfly/v2/pkg/rpc/manager"
-	"d7y.io/dragonfly/v2/pkg/util/mathutils"
 )
 
 const (
@@ -234,7 +234,7 @@ func calculateMultiElementAffinityScore(dst, src string) float64 {
 	var score, elementLen int
 	dstElements := strings.Split(dst, "|")
 	srcElements := strings.Split(src, "|")
-	elementLen = mathutils.MaxInt(len(dstElements), len(srcElements))
+	elementLen = math.MaxInt(len(dstElements), len(srcElements))
 
 	// Maximum element length is 5
 	if elementLen > maxElementLen {
diff --git a/manager/service/job.go b/manager/service/job.go
index 0a570a88..2e7df769 100644
--- a/manager/service/job.go
+++ b/manager/service/job.go
@@ -26,7 +26,7 @@ import (
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/types"
 	"d7y.io/dragonfly/v2/pkg/retry"
-	"d7y.io/dragonfly/v2/pkg/util/structutils"
+	"d7y.io/dragonfly/v2/pkg/structure"
 )
 
 func (s *service) CreatePreheatJob(ctx context.Context, json types.CreatePreheatJobRequest) (*model.Job, error) {
@@ -73,7 +73,7 @@ func (s *service) CreatePreheatJob(ctx context.Context, json types.CreatePreheat
 		return nil, err
 	}
 
-	args, err := structutils.StructToMap(json.Args)
+	args, err := structure.StructToMap(json.Args)
 	if err != nil {
 		return nil, err
 	}
diff --git a/manager/service/scheduler_cluster.go b/manager/service/scheduler_cluster.go
index 3b6066d6..c7ad445e 100644
--- a/manager/service/scheduler_cluster.go
+++ b/manager/service/scheduler_cluster.go
@@ -22,21 +22,21 @@ import (
 
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/types"
-	"d7y.io/dragonfly/v2/pkg/util/structutils"
+	"d7y.io/dragonfly/v2/pkg/structure"
 )
 
 func (s *service) CreateSchedulerCluster(ctx context.Context, json types.CreateSchedulerClusterRequest) (*model.SchedulerCluster, error) {
-	config, err := structutils.StructToMap(json.Config)
+	config, err := structure.StructToMap(json.Config)
 	if err != nil {
 		return nil, err
 	}
 
-	clientConfig, err := structutils.StructToMap(json.ClientConfig)
+	clientConfig, err := structure.StructToMap(json.ClientConfig)
 	if err != nil {
 		return nil, err
 	}
 
-	scopes, err := structutils.StructToMap(json.Scopes)
+	scopes, err := structure.StructToMap(json.Scopes)
 	if err != nil {
 		return nil, err
 	}
@@ -87,17 +87,17 @@ func (s *service) DestroySchedulerCluster(ctx context.Context, id uint) error {
 }
 
 func (s *service) UpdateSchedulerCluster(ctx context.Context, id uint, json types.UpdateSchedulerClusterRequest) (*model.SchedulerCluster, error) {
-	config, err := structutils.StructToMap(json.Config)
+	config, err := structure.StructToMap(json.Config)
 	if err != nil {
 		return nil, err
 	}
 
-	clientConfig, err := structutils.StructToMap(json.ClientConfig)
+	clientConfig, err := structure.StructToMap(json.ClientConfig)
 	if err != nil {
 		return nil, err
 	}
 
-	scopes, err := structutils.StructToMap(json.Scopes)
+	scopes, err := structure.StructToMap(json.Scopes)
 	if err != nil {
 		return nil, err
 	}
diff --git a/manager/service/seed_peer_cluster.go b/manager/service/seed_peer_cluster.go
index 5d1399e8..4d208170 100644
--- a/manager/service/seed_peer_cluster.go
+++ b/manager/service/seed_peer_cluster.go
@@ -22,16 +22,16 @@ import (
 
 	"d7y.io/dragonfly/v2/manager/model"
 	"d7y.io/dragonfly/v2/manager/types"
-	"d7y.io/dragonfly/v2/pkg/util/structutils"
+	"d7y.io/dragonfly/v2/pkg/structure"
 )
 
 func (s *service) CreateSeedPeerCluster(ctx context.Context, json types.CreateSeedPeerClusterRequest) (*model.SeedPeerCluster, error) {
-	config, err := structutils.StructToMap(json.Config)
+	config, err := structure.StructToMap(json.Config)
 	if err != nil {
 		return nil, err
 	}
 
-	scopes, err := structutils.StructToMap(json.Scopes)
+	scopes, err := structure.StructToMap(json.Scopes)
 	if err != nil {
 		return nil, err
 	}
@@ -69,12 +69,12 @@ func (s *service) DestroySeedPeerCluster(ctx context.Context, id uint) error {
 }
 
 func (s *service) UpdateSeedPeerCluster(ctx context.Context, id uint, json types.UpdateSeedPeerClusterRequest) (*model.SeedPeerCluster, error) {
-	config, err := structutils.StructToMap(json.Config)
+	config, err := structure.StructToMap(json.Config)
 	if err != nil {
 		return nil, err
 	}
 
-	scopes, err := structutils.StructToMap(json.Scopes)
+	scopes, err := structure.StructToMap(json.Scopes)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/basic/user.go b/pkg/basic/user.go
index 957bb3bd..0bf1f6b1 100644
--- a/pkg/basic/user.go
+++ b/pkg/basic/user.go
@@ -25,7 +25,7 @@ import (
 	"syscall"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
 )
 
 var (
@@ -53,14 +53,14 @@ func init() {
 
 		HomeDir = u.HomeDir
 		HomeDir = strings.TrimSpace(HomeDir)
-		if stringutils.IsBlank(HomeDir) {
+		if pkgstrings.IsBlank(HomeDir) {
 			panic("home dir is empty")
 		}
 	}
 
 	TmpDir = os.TempDir()
 	TmpDir = strings.TrimSpace(TmpDir)
-	if stringutils.IsBlank(TmpDir) {
+	if pkgstrings.IsBlank(TmpDir) {
 		TmpDir = "/tmp"
 	}
 }
diff --git a/pkg/util/digestutils/digest.go b/pkg/digest/digest.go
similarity index 97%
rename from pkg/util/digestutils/digest.go
rename to pkg/digest/digest.go
index 31c39d17..f291e2cd 100644
--- a/pkg/util/digestutils/digest.go
+++ b/pkg/digest/digest.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package digestutils
+package digest
 
 import (
 	"bufio"
diff --git a/pkg/util/digestutils/digest_reader.go b/pkg/digest/digest_reader.go
similarity index 82%
rename from pkg/util/digestutils/digest_reader.go
rename to pkg/digest/digest_reader.go
index da639f23..c299823b 100644
--- a/pkg/util/digestutils/digest_reader.go
+++ b/pkg/digest/digest_reader.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package digestutils
+package digest
 
 import (
 	"crypto/md5"
@@ -36,22 +36,22 @@ var (
 	ErrDigestNotMatch = errors.New("digest not match")
 )
 
-// digestReader reads stream with RateLimiter.
-type digestReader struct {
+// reader reads stream with RateLimiter.
+type reader struct {
 	r      io.Reader
 	hash   hash.Hash
 	digest string
 	*logger.SugaredLoggerOnWith
 }
 
-type DigestReader interface {
+// Reader is the interface used for reading resource.
+type Reader interface {
 	io.Reader
 	Digest() string
 }
 
 // TODO add AF_ALG digest https://github.com/golang/sys/commit/e24f485414aeafb646f6fca458b0bf869c0880a1
-
-func NewDigestReader(log *logger.SugaredLoggerOnWith, reader io.Reader, digest ...string) (io.Reader, error) {
+func NewReader(log *logger.SugaredLoggerOnWith, r io.Reader, digest ...string) (io.Reader, error) {
 	var (
 		d          string
 		hashMethod hash.Hash
@@ -77,15 +77,15 @@ func NewDigestReader(log *logger.SugaredLoggerOnWith, reader io.Reader, digest .
 	} else {
 		hashMethod = md5.New()
 	}
-	return &digestReader{
+	return &reader{
 		SugaredLoggerOnWith: log,
 		digest:              d,
 		hash:                hashMethod,
-		r:                   reader,
+		r:                   r,
 	}, nil
 }
 
-func (dr *digestReader) Read(p []byte) (int, error) {
+func (dr *reader) Read(p []byte) (int, error) {
 	n, err := dr.r.Read(p)
 	if err != nil && err != io.EOF {
 		return n, err
@@ -105,6 +105,6 @@ func (dr *digestReader) Read(p []byte) (int, error) {
 }
 
 // Digest returns the digest of contents.
-func (dr *digestReader) Digest() string {
+func (dr *reader) Digest() string {
 	return hex.EncodeToString(dr.hash.Sum(nil))
 }
diff --git a/pkg/idgen/task_id.go b/pkg/idgen/task_id.go
index 0aa938c2..5e876ea3 100644
--- a/pkg/idgen/task_id.go
+++ b/pkg/idgen/task_id.go
@@ -19,35 +19,15 @@ package idgen
 import (
 	"strings"
 
+	"d7y.io/dragonfly/v2/pkg/digest"
+	neturl "d7y.io/dragonfly/v2/pkg/net/url"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/urlutils"
+	pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
 )
 
-func taskID(url string, meta *base.UrlMeta, ignoreRange bool) string {
-	var filters []string
-	if meta != nil && meta.Filter != "" {
-		filters = strings.Split(meta.Filter, "&")
-	}
-
-	var data []string
-	data = append(data, urlutils.FilterURLParam(url, filters))
-	if meta != nil {
-		if meta.Digest != "" {
-			data = append(data, meta.Digest)
-		}
-
-		if !ignoreRange && meta.Range != "" {
-			data = append(data, meta.Range)
-		}
-
-		if meta.Tag != "" {
-			data = append(data, meta.Tag)
-		}
-	}
-
-	return digestutils.Sha256(data...)
-}
+const (
+	filterSeparator = "&"
+)
 
 // TaskID generates a task id.
 // filter is separated by & character.
@@ -60,3 +40,46 @@ func TaskID(url string, meta *base.UrlMeta) string {
 func ParentTaskID(url string, meta *base.UrlMeta) string {
 	return taskID(url, meta, true)
 }
+
+// taskID generates a task id.
+// filter is separated by & character.
+func taskID(url string, meta *base.UrlMeta, ignoreRange bool) string {
+	if meta == nil {
+		return digest.Sha256(url)
+	}
+
+	filters := parseFilters(meta.Filter)
+
+	var (
+		u   string
+		err error
+	)
+	u, err = neturl.FilterQuery(url, filters)
+	if err != nil {
+		u = ""
+	}
+
+	data := []string{u}
+	if meta.Digest != "" {
+		data = append(data, meta.Digest)
+	}
+
+	if !ignoreRange && meta.Range != "" {
+		data = append(data, meta.Range)
+	}
+
+	if meta.Tag != "" {
+		data = append(data, meta.Tag)
+	}
+
+	return digest.Sha256(data...)
+}
+
+// parseFilters parses a filter string to filter slice.
+func parseFilters(rawFilters string) []string {
+	if pkgstrings.IsBlank(rawFilters) {
+		return nil
+	}
+
+	return strings.Split(rawFilters, filterSeparator)
+}
diff --git a/pkg/util/mathutils/math_utils.go b/pkg/math/math.go
similarity index 63%
rename from pkg/util/mathutils/math_utils.go
rename to pkg/math/math.go
index c2b0af56..c3c211a5 100644
--- a/pkg/util/mathutils/math_utils.go
+++ b/pkg/math/math.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,15 +14,7 @@
  * limitations under the License.
  */
 
-// Package mathutils provides utilities supplementing the standard 'math' and 'math/rand' packages.
-package mathutils
-
-import (
-	"math"
-	"strconv"
-)
-
-const float64EqualityThreshold = 1e-9
+package math
 
 func MaxInt(a, b int) int {
 	if a < b {
@@ -40,15 +32,15 @@ func MaxInt32(a, b int32) int32 {
 	return b
 }
 
-func MinInt(a, b int) int {
-	if a < b {
+func MaxInt64(a, b int64) int64 {
+	if a > b {
 		return a
 	}
 
 	return b
 }
 
-func MinInt32(a, b int32) int32 {
+func MinInt(a, b int) int {
 	if a < b {
 		return a
 	}
@@ -56,8 +48,8 @@ func MinInt32(a, b int32) int32 {
 	return b
 }
 
-func MaxInt64(a, b int64) int64 {
-	if a > b {
+func MinInt32(a, b int32) int32 {
+	if a < b {
 		return a
 	}
 
@@ -71,23 +63,3 @@ func MinInt64(a, b int64) int64 {
 
 	return b
 }
-
-func IsNatural(value string) bool {
-	if v, err := strconv.ParseInt(value, 0, 64); err == nil {
-		return v >= 0
-	}
-
-	return false
-}
-
-func IsInteger(value string) bool {
-	if _, err := strconv.ParseInt(value, 0, 64); err == nil {
-		return true
-	}
-
-	return false
-}
-
-func EqualFloat64(a, b float64) bool {
-	return math.Abs(a-b) <= float64EqualityThreshold
-}
diff --git a/pkg/math/rand.go b/pkg/math/rand.go
new file mode 100644
index 00000000..58c3147e
--- /dev/null
+++ b/pkg/math/rand.go
@@ -0,0 +1,56 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package math
+
+import (
+	"math"
+	"math/rand"
+	"time"
+)
+
+func init() {
+	rand.Seed(time.Now().UnixNano())
+}
+
+const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+const (
+	letterIdxBits = 6                    // 6 bits to represent a letter index
+	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
+	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
+)
+
+func RandString(n int) string {
+	b := make([]byte, n)
+	for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
+		if remain == 0 {
+			cache, remain = rand.Int63(), letterIdxMax
+		}
+		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
+			b[i] = letterBytes[idx]
+			i--
+		}
+		cache >>= letterIdxBits
+		remain--
+	}
+
+	return string(b)
+}
+
+func RandBackoff(initBackoff float64, maxBackoff float64, base float64, exp int) time.Duration {
+	m := math.Max(initBackoff, rand.Float64()*math.Min(math.Pow(base, float64(exp))*initBackoff, maxBackoff))
+	return time.Duration(m * float64(time.Second))
+}
diff --git a/pkg/util/hostutils/host_utils.go b/pkg/net/fqdn/fqdn.go
similarity index 77%
rename from pkg/util/hostutils/host_utils.go
rename to pkg/net/fqdn/fqdn.go
index 0dcced1f..883bb853 100644
--- a/pkg/util/hostutils/host_utils.go
+++ b/pkg/net/fqdn/fqdn.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,32 +14,18 @@
  * limitations under the License.
  */
 
-package hostutils
+package fqdn
 
 import (
-	"os"
-
 	"github.com/Showmax/go-fqdn"
 )
 
-var Hostname string
 var FQDNHostname string
 
 func init() {
-	Hostname = hostname()
 	FQDNHostname = fqdnHostname()
 }
 
-// Get kernel hostname
-func hostname() string {
-	name, err := os.Hostname()
-	if err != nil {
-		panic(err)
-	}
-
-	return name
-}
-
 // Get FQDN hostname
 func fqdnHostname() string {
 	fqdn, err := fqdn.FqdnHostname()
diff --git a/pkg/util/net/httputils/http_utils.go b/pkg/net/http/http.go
similarity index 95%
rename from pkg/util/net/httputils/http_utils.go
rename to pkg/net/http/http.go
index fc635db0..6b05ab71 100644
--- a/pkg/util/net/httputils/http_utils.go
+++ b/pkg/net/http/http.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package httputils
+package http
 
 import "net/http"
 
diff --git a/pkg/util/rangeutils/range.go b/pkg/net/http/range.go
similarity index 98%
rename from pkg/util/rangeutils/range.go
rename to pkg/net/http/range.go
index 6e3af269..0072de7e 100644
--- a/pkg/util/rangeutils/range.go
+++ b/pkg/net/http/range.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package rangeutils
+package http
 
 import (
 	"fmt"
diff --git a/pkg/util/net/iputils/ip_utils.go b/pkg/net/ip/ip.go
similarity index 66%
rename from pkg/util/net/iputils/ip_utils.go
rename to pkg/net/ip/ip.go
index 4bbd7bf3..40fd3d22 100644
--- a/pkg/util/net/iputils/ip_utils.go
+++ b/pkg/net/ip/ip.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-package iputils
+package ip
 
 import (
 	"net"
 	"os"
-	"sort"
 
 	"github.com/pkg/errors"
 
@@ -28,7 +27,9 @@ import (
 
 var IPv4 string
 
-const internalIPv4 string = "127.0.0.1"
+const (
+	internalIPv4 = "127.0.0.1"
+)
 
 func init() {
 	ip, err := externalIPv4()
@@ -41,42 +42,27 @@ func init() {
 	}
 }
 
-// IsIPv4 returns whether the ip is a valid IPv4 Address.
-func IsIPv4(ip string) bool {
-	addr := net.ParseIP(ip)
-	if addr != nil {
-		return addr.To4() != nil
-	}
-
-	return false
-}
-
-// externalIPv4 returns the first IPv4 available.
+// externalIPv4 returns the available IPv4.
 func externalIPv4() (string, error) {
 	ips, err := ipAddrs()
-
 	if err != nil {
 		return "", err
 	}
 
-	if len(ips) == 0 {
-		return "", errors.Errorf("not found any ip")
-	}
-
-	var values []string
+	var externalIPs []string
 	for _, ip := range ips {
 		ip = ip.To4()
 		if ip == nil {
 			continue // not an ipv4 address
 		}
-		values = append(values, ip.String())
+		externalIPs = append(externalIPs, ip.String())
 	}
 
-	if len(values) > 0 {
-		return values[0], nil
+	if len(externalIPs) == 0 {
+		return "", errors.Errorf("can not found external ipv4")
 	}
 
-	return "", errors.Errorf("not found any ipv4")
+	return externalIPs[0], nil
 }
 
 // ipAddrs returns all the valid IPs available.
@@ -86,12 +72,12 @@ func ipAddrs() ([]net.IP, error) {
 	var prefers []net.IP
 	host, err := os.Hostname()
 	if err == nil {
-		addrs, _ := net.LookupIP(host)
-		for _, addr := range addrs {
-			if addr == nil || addr.IsLoopback() || addr.IsLinkLocalUnicast() {
+		ips, _ := net.LookupIP(host)
+		for _, ip := range ips {
+			if ip == nil || ip.IsLoopback() || ip.IsLinkLocalUnicast() {
 				continue
 			}
-			prefers = append(prefers, addr)
+			prefers = append(prefers, ip)
 		}
 	}
 
@@ -100,8 +86,8 @@ func ipAddrs() ([]net.IP, error) {
 		return nil, err
 	}
 
-	var preferAddrs []net.IP
-	var normalAddrs []net.IP
+	var preferIPs []net.IP
+	var normalIPs []net.IP
 	for _, iface := range ifaces {
 		if iface.Flags&net.FlagUp == 0 {
 			continue // interface down
@@ -126,26 +112,21 @@ func ipAddrs() ([]net.IP, error) {
 			if ip == nil || ip.IsLoopback() || ip.IsLinkLocalUnicast() {
 				continue
 			}
+
 			var isPrefer bool
 			for _, preferAddr := range prefers {
 				if preferAddr.Equal(ip) {
 					isPrefer = true
 				}
 			}
+
 			if isPrefer {
-				preferAddrs = append(preferAddrs, ip)
+				preferIPs = append(preferIPs, ip)
 			} else {
-				normalAddrs = append(normalAddrs, ip)
+				normalIPs = append(normalIPs, ip)
 			}
 		}
 	}
-	sortIP(preferAddrs)
-	sortIP(normalAddrs)
-	return append(preferAddrs, normalAddrs...), nil
-}
 
-func sortIP(addrs []net.IP) {
-	sort.Slice(addrs, func(i, j int) bool {
-		return addrs[i].String() < addrs[j].String()
-	})
+	return append(preferIPs, normalIPs...), nil
 }
diff --git a/pkg/util/net/urlutils/url_utils.go b/pkg/net/url/url.go
similarity index 61%
rename from pkg/util/net/urlutils/url_utils.go
rename to pkg/net/url/url.go
index 44b35dc2..7cfb985a 100644
--- a/pkg/util/net/urlutils/url_utils.go
+++ b/pkg/net/url/url.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,38 +14,44 @@
  * limitations under the License.
  */
 
-package urlutils
+package url
 
 import (
 	"net/url"
-
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 )
 
-// FilterURLParam excludes queries in url with filters.
-func FilterURLParam(str string, filters []string) string {
-	if len(filters) == 0 || len(str) == 0 {
-		return str
+// FilterQuery excludes query string in url with filters.
+func FilterQuery(rawURL string, filters []string) (string, error) {
+	if len(filters) == 0 {
+		return rawURL, nil
 	}
 
-	u, err := url.Parse(str)
+	u, err := url.Parse(rawURL)
 	if err != nil {
-		return ""
+		return "", err
 	}
 
 	var values = make(url.Values)
 	for k, v := range u.Query() {
-		if !stringutils.ContainsFold(filters, k) {
+		var isFilter bool
+		for _, filter := range filters {
+			if k == filter {
+				isFilter = true
+				break
+			}
+		}
+
+		if !isFilter {
 			values[k] = v
 		}
 	}
 
 	u.RawQuery = values.Encode()
-	return u.String()
+	return u.String(), nil
 }
 
-// IsValidURL returns whether the string url is a valid URL.
-func IsValidURL(str string) bool {
+// IsValid returns whether the string url is a valid URL.
+func IsValid(str string) bool {
 	u, err := url.Parse(str)
 	return err == nil && u.Scheme != "" && u.Host != ""
 }
diff --git a/pkg/retry/retry.go b/pkg/retry/retry.go
index a84a09d4..85e061dd 100644
--- a/pkg/retry/retry.go
+++ b/pkg/retry/retry.go
@@ -20,7 +20,7 @@ import (
 	"context"
 	"time"
 
-	"d7y.io/dragonfly/v2/pkg/util/mathutils"
+	"d7y.io/dragonfly/v2/pkg/math"
 )
 
 func Run(ctx context.Context,
@@ -35,7 +35,7 @@ func Run(ctx context.Context,
 	)
 	for i := 0; i < maxAttempts; i++ {
 		if i > 0 {
-			time.Sleep(mathutils.RandBackoff(initBackoff, maxBackoff, 2.0, i))
+			time.Sleep(math.RandBackoff(initBackoff, maxBackoff, 2.0, i))
 		}
 
 		res, cancel, cause = f()
diff --git a/pkg/rpc/client_util.go b/pkg/rpc/client_util.go
index 31ea99c0..9756af7f 100644
--- a/pkg/rpc/client_util.go
+++ b/pkg/rpc/client_util.go
@@ -30,8 +30,8 @@ import (
 
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/math"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
-	"d7y.io/dragonfly/v2/pkg/util/mathutils"
 )
 
 const (
@@ -223,7 +223,7 @@ func ExecuteWithRetry(f func() (interface{}, error), initBackoff float64, maxBac
 			return res, cause
 		}
 		if i > 0 {
-			time.Sleep(mathutils.RandBackoff(initBackoff, maxBackoff, 2.0, i))
+			time.Sleep(math.RandBackoff(initBackoff, maxBackoff, 2.0, i))
 		}
 
 		res, cause = f()
diff --git a/pkg/safe/safe.go b/pkg/safe/safe.go
index 1d6c89ca..709e2baa 100644
--- a/pkg/safe/safe.go
+++ b/pkg/safe/safe.go
@@ -23,12 +23,12 @@ import (
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 )
 
-// safe call function
+// Safe call function.
 func Call(f func()) (err error) {
 	defer func() {
-		if desc := recover(); desc != nil {
-			err = fmt.Errorf("%v", desc)
-			logger.Errorf("panic stack: %s", string(debug.Stack()))
+		if r := recover(); r != nil {
+			err = fmt.Errorf("%v", r)
+			logger.Errorf("panic: %s", string(debug.Stack()))
 			debug.PrintStack()
 		}
 	}()
diff --git a/pkg/source/clients/hdfsprotocol/hdfs_source_client.go b/pkg/source/clients/hdfsprotocol/hdfs_source_client.go
index 4859a300..2c6d8596 100644
--- a/pkg/source/clients/hdfsprotocol/hdfs_source_client.go
+++ b/pkg/source/clients/hdfsprotocol/hdfs_source_client.go
@@ -27,8 +27,8 @@ import (
 	"github.com/colinmarc/hdfs/v2"
 	"github.com/pkg/errors"
 
+	"d7y.io/dragonfly/v2/pkg/net/http"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 )
 
 const (
@@ -131,7 +131,7 @@ func (h *hdfsSourceClient) Download(request *source.Request) (*source.Response,
 	}
 
 	if request.Header.Get(source.Range) != "" {
-		requestRange, err := rangeutils.ParseRange(request.Header.Get(source.Range), uint64(limitReadN))
+		requestRange, err := http.ParseRange(request.Header.Get(source.Range), uint64(limitReadN))
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/source/clients/ossprotocol/oss_source_client.go b/pkg/source/clients/ossprotocol/oss_source_client.go
index 533826ae..2d52d6fe 100644
--- a/pkg/source/clients/ossprotocol/oss_source_client.go
+++ b/pkg/source/clients/ossprotocol/oss_source_client.go
@@ -28,7 +28,7 @@ import (
 	"github.com/pkg/errors"
 
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	"d7y.io/dragonfly/v2/pkg/strings"
 )
 
 const OSSClient = "oss"
@@ -204,15 +204,15 @@ func (osc *ossSourceClient) GetLastModified(request *source.Request) (int64, err
 
 func (osc *ossSourceClient) getClient(header source.Header) (*oss.Client, error) {
 	endpoint := header.Get(endpoint)
-	if stringutils.IsBlank(endpoint) {
+	if strings.IsBlank(endpoint) {
 		return nil, errors.New("endpoint is empty")
 	}
 	accessKeyID := header.Get(accessKeyID)
-	if stringutils.IsBlank(accessKeyID) {
+	if strings.IsBlank(accessKeyID) {
 		return nil, errors.New("accessKeyID is empty")
 	}
 	accessKeySecret := header.Get(accessKeySecret)
-	if stringutils.IsBlank(accessKeySecret) {
+	if strings.IsBlank(accessKeySecret) {
 		return nil, errors.New("accessKeySecret is empty")
 	}
 	clientKey := buildClientKey(endpoint, accessKeyID, accessKeySecret)
diff --git a/pkg/util/mathutils/random.go b/pkg/strings/strings.go
similarity index 59%
rename from pkg/util/mathutils/random.go
rename to pkg/strings/strings.go
index 11d2991d..a7ae13b8 100644
--- a/pkg/util/mathutils/random.go
+++ b/pkg/strings/strings.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,22 +14,24 @@
  * limitations under the License.
  */
 
-package mathutils
+package strings
 
 import (
-	"math"
-	"math/rand"
-	"time"
+	"strings"
 )
 
-func init() {
-	rand.Seed(time.Now().UnixNano())
+// IsBlank determines whether the string is empty.
+func IsBlank(s string) bool {
+	return strings.TrimSpace(s) == ""
 }
 
-func RandBackoff(initBackoff float64, maxBackoff float64, base float64, exp int) time.Duration {
-	v1 := math.Pow(base, float64(exp))
+// Contains reports whether the string contains the element.
+func Contains(slice []string, ele string) bool {
+	for _, one := range slice {
+		if one == ele {
+			return true
+		}
+	}
 
-	v2 := math.Max(initBackoff, rand.Float64()*math.Min(v1*initBackoff, maxBackoff))
-
-	return time.Duration(v2 * float64(time.Second))
+	return false
 }
diff --git a/pkg/util/structutils/struct_utils.go b/pkg/structure/structure.go
similarity index 84%
rename from pkg/util/structutils/struct_utils.go
rename to pkg/structure/structure.go
index 73894b54..30a3ca95 100644
--- a/pkg/util/structutils/struct_utils.go
+++ b/pkg/structure/structure.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-// Package structutils provides utilities supplementing the standard 'time' package.
-package structutils
+package structure
 
 import (
 	"encoding/json"
 )
 
+// StructToMap coverts struct to map.
 func StructToMap(t interface{}) (map[string]interface{}, error) {
 	var m map[string]interface{}
 	b, err := json.Marshal(t)
diff --git a/pkg/util/timeutils/time_utils.go b/pkg/time/time.go
similarity index 85%
rename from pkg/util/timeutils/time_utils.go
rename to pkg/time/time.go
index 2eaec295..ae537bb0 100644
--- a/pkg/util/timeutils/time_utils.go
+++ b/pkg/time/time.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-// Package timeutils provides utilities supplementing the standard 'time' package.
-package timeutils
+package time
 
 import (
 	"time"
diff --git a/pkg/unit/byte.go b/pkg/unit/byte.go
index a28ca1aa..0e70599b 100644
--- a/pkg/unit/byte.go
+++ b/pkg/unit/byte.go
@@ -21,12 +21,11 @@ import (
 	"fmt"
 	"regexp"
 	"strconv"
-	"strings"
 
 	"github.com/pkg/errors"
 	"gopkg.in/yaml.v3"
 
-	"d7y.io/dragonfly/v2/pkg/util/stringutils"
+	pkgstrings "d7y.io/dragonfly/v2/pkg/strings"
 )
 
 type Bytes int64
@@ -51,7 +50,7 @@ func ToBytes(size int64) Bytes {
 
 // Set is used for command flag var
 func (f *Bytes) Set(s string) (err error) {
-	if stringutils.IsBlank(s) {
+	if pkgstrings.IsBlank(s) {
 		*f = 0
 	} else {
 		*f, err = parseSize(s)
@@ -96,8 +95,7 @@ func (f Bytes) String() string {
 var sizeRegexp = regexp.MustCompile(`^([0-9]+)(\.0*)?([MmKkGgTtPpEe])?[iI]?[bB]?$`)
 
 func parseSize(fsize string) (Bytes, error) {
-	fsize = strings.TrimSpace(fsize)
-	if stringutils.IsBlank(fsize) {
+	if pkgstrings.IsBlank(fsize) {
 		return 0, nil
 	}
 
diff --git a/pkg/util/stringutils/string_utils.go b/pkg/util/stringutils/string_utils.go
deleted file mode 100644
index 18152358..00000000
--- a/pkg/util/stringutils/string_utils.go
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// Package stringutils provides utilities supplementing the standard 'strings' package.
-package stringutils
-
-import (
-	"math/rand"
-	"strings"
-	"time"
-	"unicode"
-)
-
-func SubString(str string, start, end int) string {
-	runes := []rune(str)
-	length := len(runes)
-	if start < 0 || start >= length || end <= 0 || end > length || start >= end {
-		return ""
-	}
-
-	return string(runes[start:end])
-}
-
-func IsBlank(str string) bool {
-	for _, c := range str {
-		if !unicode.IsSpace(c) {
-			return false
-		}
-	}
-
-	return true
-}
-
-func IsEmpty(str string) bool {
-	return str == ""
-}
-
-func ContainsFold(slice []string, ele string) bool {
-	for _, one := range slice {
-		if strings.EqualFold(one, ele) {
-			return true
-		}
-	}
-
-	return false
-}
-
-func Contains(slice []string, ele string) bool {
-	for _, one := range slice {
-		if one == ele {
-			return true
-		}
-	}
-
-	return false
-}
-
-func RandString(len int) string {
-	r := rand.New(rand.NewSource(time.Now().UnixNano()))
-	bytes := make([]byte, len)
-	for i := 0; i < len; i++ {
-		b := r.Intn(26) + 65
-		bytes[i] = byte(b)
-	}
-	return string(bytes)
-}
diff --git a/scheduler/config/config.go b/scheduler/config/config.go
index 2bfd8e22..7a73f484 100644
--- a/scheduler/config/config.go
+++ b/scheduler/config/config.go
@@ -22,8 +22,8 @@ import (
 	"github.com/pkg/errors"
 
 	"d7y.io/dragonfly/v2/cmd/dependency/base"
-	"d7y.io/dragonfly/v2/pkg/util/hostutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
+	"d7y.io/dragonfly/v2/pkg/net/fqdn"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/scheduler/storage"
 )
 
@@ -63,8 +63,8 @@ type Config struct {
 func New() *Config {
 	return &Config{
 		Server: &ServerConfig{
-			IP:     iputils.IPv4,
-			Host:   hostutils.FQDNHostname,
+			IP:     ip.IPv4,
+			Host:   fqdn.FQDNHostname,
 			Listen: "0.0.0.0",
 			Port:   8002,
 		},
diff --git a/scheduler/resource/seed_peer.go b/scheduler/resource/seed_peer.go
index e6875701..a5f9b3b3 100644
--- a/scheduler/resource/seed_peer.go
+++ b/scheduler/resource/seed_peer.go
@@ -27,7 +27,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/base/common"
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem"
 	rpcscheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler"
-	"d7y.io/dragonfly/v2/pkg/util/timeutils"
+	pkgtime "d7y.io/dragonfly/v2/pkg/time"
 )
 
 const (
@@ -131,7 +131,7 @@ func (s *seedPeer) TriggerTask(ctx context.Context, task *Task) (*Peer, *rpcsche
 		// Handle piece download successfully.
 		peer.Log.Infof("receive piece from seed peer: %#v %#v", piece, piece.PieceInfo)
 		peer.Pieces.Set(uint(piece.PieceInfo.PieceNum))
-		peer.AppendPieceCost(timeutils.SubNano(int64(piece.EndTime), int64(piece.BeginTime)).Milliseconds())
+		peer.AppendPieceCost(pkgtime.SubNano(int64(piece.EndTime), int64(piece.BeginTime)).Milliseconds())
 		task.StorePiece(piece.PieceInfo)
 	}
 }
diff --git a/scheduler/scheduler/evaluator/evaluator_base.go b/scheduler/scheduler/evaluator/evaluator_base.go
index a8aea565..61327b05 100644
--- a/scheduler/scheduler/evaluator/evaluator_base.go
+++ b/scheduler/scheduler/evaluator/evaluator_base.go
@@ -23,7 +23,7 @@ import (
 	"github.com/montanaflynn/stats"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
-	"d7y.io/dragonfly/v2/pkg/util/mathutils"
+	"d7y.io/dragonfly/v2/pkg/math"
 	"d7y.io/dragonfly/v2/scheduler/resource"
 )
 
@@ -159,7 +159,7 @@ func calculateMultiElementAffinityScore(dst, src string) float64 {
 	var score, elementLen int
 	dstElements := strings.Split(dst, "|")
 	srcElements := strings.Split(src, "|")
-	elementLen = mathutils.MaxInt(len(dstElements), len(srcElements))
+	elementLen = math.MaxInt(len(dstElements), len(srcElements))
 
 	// Maximum element length is 5.
 	if elementLen > maxElementLen {
diff --git a/scheduler/service/service.go b/scheduler/service/service.go
index b983b83d..3771fa8e 100644
--- a/scheduler/service/service.go
+++ b/scheduler/service/service.go
@@ -30,7 +30,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/base/common"
 	rpcscheduler "d7y.io/dragonfly/v2/pkg/rpc/scheduler"
-	"d7y.io/dragonfly/v2/pkg/util/timeutils"
+	pkgtime "d7y.io/dragonfly/v2/pkg/time"
 	"d7y.io/dragonfly/v2/scheduler/config"
 	"d7y.io/dragonfly/v2/scheduler/metrics"
 	"d7y.io/dragonfly/v2/scheduler/resource"
@@ -644,7 +644,7 @@ func (s *Service) handleEndOfPiece(ctx context.Context, peer *resource.Peer) {}
 func (s *Service) handlePieceSuccess(ctx context.Context, peer *resource.Peer, piece *rpcscheduler.PieceResult) {
 	// Update peer piece info
 	peer.Pieces.Set(uint(piece.PieceInfo.PieceNum))
-	peer.AppendPieceCost(timeutils.SubNano(int64(piece.EndTime), int64(piece.BeginTime)).Milliseconds())
+	peer.AppendPieceCost(pkgtime.SubNano(int64(piece.EndTime), int64(piece.BeginTime)).Milliseconds())
 
 	// When the peer downloads back-to-source,
 	// piece downloads successfully updates the task piece info.


Test patch:
diff --git a/client/daemon/peer/peertask_manager_test.go b/client/daemon/peer/peertask_manager_test.go
index 39b9caec..4207b83e 100644
--- a/client/daemon/peer/peertask_manager_test.go
+++ b/client/daemon/peer/peertask_manager_test.go
@@ -50,6 +50,7 @@ import (
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/idgen"
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
@@ -62,7 +63,6 @@ import (
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/source/clients/httpprotocol"
 	sourceMock "d7y.io/dragonfly/v2/pkg/source/mock"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 func TestMain(m *testing.M) {
@@ -95,9 +95,9 @@ func setupPeerTaskManagerComponents(ctrl *gomock.Controller, opt componentsOptio
 	r := bytes.NewBuffer(opt.content)
 	var pieces = make([]string, int(math.Ceil(float64(len(opt.content))/float64(opt.pieceSize))))
 	for i := range pieces {
-		pieces[i] = digestutils.Md5Reader(io.LimitReader(r, int64(opt.pieceSize)))
+		pieces[i] = digest.Md5Reader(io.LimitReader(r, int64(opt.pieceSize)))
 	}
-	totalDigests := digestutils.Sha256(pieces...)
+	totalDigests := digest.Sha256(pieces...)
 	genPiecePacket := func(request *base.PieceTaskRequest) *base.PiecePacket {
 		var tasks []*base.PieceInfo
 		for i := uint32(0); i < request.Limit; i++ {
diff --git a/client/daemon/peer/peertask_stream_backsource_partial_test.go b/client/daemon/peer/peertask_stream_backsource_partial_test.go
index 20853a33..050e901a 100644
--- a/client/daemon/peer/peertask_stream_backsource_partial_test.go
+++ b/client/daemon/peer/peertask_stream_backsource_partial_test.go
@@ -45,6 +45,7 @@ import (
 	mock_daemon "d7y.io/dragonfly/v2/client/daemon/test/mock/daemon"
 	"d7y.io/dragonfly/v2/internal/dferrors"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
@@ -56,7 +57,6 @@ import (
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/source/clients/httpprotocol"
 	sourceMock "d7y.io/dragonfly/v2/pkg/source/mock"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 func setupBackSourcePartialComponents(ctrl *gomock.Controller, testBytes []byte, opt componentsOption) (
@@ -69,9 +69,9 @@ func setupBackSourcePartialComponents(ctrl *gomock.Controller, testBytes []byte,
 	pieceCount := int32(math.Ceil(float64(opt.contentLength) / float64(opt.pieceSize)))
 	for i := int32(0); i < pieceCount; i++ {
 		if int64(i+1)*int64(opt.pieceSize) > opt.contentLength {
-			piecesMd5 = append(piecesMd5, digestutils.Md5Bytes(testBytes[int(i)*int(opt.pieceSize):]))
+			piecesMd5 = append(piecesMd5, digest.Md5Bytes(testBytes[int(i)*int(opt.pieceSize):]))
 		} else {
-			piecesMd5 = append(piecesMd5, digestutils.Md5Bytes(testBytes[int(i)*int(opt.pieceSize):int(i+1)*int(opt.pieceSize)]))
+			piecesMd5 = append(piecesMd5, digest.Md5Bytes(testBytes[int(i)*int(opt.pieceSize):int(i+1)*int(opt.pieceSize)]))
 		}
 	}
 	daemon.EXPECT().GetPieceTasks(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(func(ctx context.Context, request *base.PieceTaskRequest) (*base.PiecePacket, error) {
@@ -83,13 +83,13 @@ func setupBackSourcePartialComponents(ctrl *gomock.Controller, testBytes []byte,
 					PieceNum:    int32(request.StartNum),
 					RangeStart:  uint64(0),
 					RangeSize:   opt.pieceSize,
-					PieceMd5:    digestutils.Md5Bytes(testBytes[0:opt.pieceSize]),
+					PieceMd5:    digest.Md5Bytes(testBytes[0:opt.pieceSize]),
 					PieceOffset: 0,
 					PieceStyle:  0,
 				})
 		}
 		return &base.PiecePacket{
-			PieceMd5Sign:  digestutils.Sha256(piecesMd5...),
+			PieceMd5Sign:  digest.Sha256(piecesMd5...),
 			TaskId:        request.TaskId,
 			DstPid:        "peer-x",
 			PieceInfos:    tasks,
diff --git a/client/daemon/rpcserver/rpcserver_test.go b/client/daemon/rpcserver/rpcserver_test.go
index 780f9ed8..9129fd81 100644
--- a/client/daemon/rpcserver/rpcserver_test.go
+++ b/client/daemon/rpcserver/rpcserver_test.go
@@ -37,12 +37,12 @@ import (
 	mock_storage "d7y.io/dragonfly/v2/client/daemon/test/mock/storage"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
 	"d7y.io/dragonfly/v2/pkg/idgen"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	dfdaemongrpc "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon"
 	dfclient "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/client"
 	dfdaemonserver "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
 func TestMain(m *testing.M) {
@@ -161,8 +161,8 @@ func Test_ServePeer(t *testing.T) {
 		{
 			request: &base.PieceTaskRequest{
 				TaskId:   idgen.TaskID("http://www.test.com", &base.UrlMeta{}),
-				SrcPid:   idgen.PeerID(iputils.IPv4),
-				DstPid:   idgen.PeerID(iputils.IPv4),
+				SrcPid:   idgen.PeerID(ip.IPv4),
+				DstPid:   idgen.PeerID(ip.IPv4),
 				StartNum: 0,
 				Limit:    1,
 			},
@@ -172,8 +172,8 @@ func Test_ServePeer(t *testing.T) {
 		{
 			request: &base.PieceTaskRequest{
 				TaskId:   idgen.TaskID("http://www.test.com", &base.UrlMeta{}),
-				SrcPid:   idgen.PeerID(iputils.IPv4),
-				DstPid:   idgen.PeerID(iputils.IPv4),
+				SrcPid:   idgen.PeerID(ip.IPv4),
+				DstPid:   idgen.PeerID(ip.IPv4),
 				StartNum: 0,
 				Limit:    4,
 			},
@@ -183,8 +183,8 @@ func Test_ServePeer(t *testing.T) {
 		{
 			request: &base.PieceTaskRequest{
 				TaskId:   idgen.TaskID("http://www.test.com", &base.UrlMeta{}),
-				SrcPid:   idgen.PeerID(iputils.IPv4),
-				DstPid:   idgen.PeerID(iputils.IPv4),
+				SrcPid:   idgen.PeerID(ip.IPv4),
+				DstPid:   idgen.PeerID(ip.IPv4),
 				StartNum: 8,
 				Limit:    1,
 			},
@@ -194,8 +194,8 @@ func Test_ServePeer(t *testing.T) {
 		{
 			request: &base.PieceTaskRequest{
 				TaskId:   idgen.TaskID("http://www.test.com", &base.UrlMeta{}),
-				SrcPid:   idgen.PeerID(iputils.IPv4),
-				DstPid:   idgen.PeerID(iputils.IPv4),
+				SrcPid:   idgen.PeerID(ip.IPv4),
+				DstPid:   idgen.PeerID(ip.IPv4),
 				StartNum: 8,
 				Limit:    4,
 			},
@@ -499,8 +499,8 @@ func Test_SyncPieceTasks(t *testing.T) {
 					},
 					&base.PieceTaskRequest{
 						TaskId:   tc.name,
-						SrcPid:   idgen.PeerID(iputils.IPv4),
-						DstPid:   idgen.PeerID(iputils.IPv4),
+						SrcPid:   idgen.PeerID(ip.IPv4),
+						DstPid:   idgen.PeerID(ip.IPv4),
 						StartNum: 0,
 						Limit:    tc.limit,
 					})
@@ -518,8 +518,8 @@ func Test_SyncPieceTasks(t *testing.T) {
 						for _, n := range tc.requestPieces {
 							request := &base.PieceTaskRequest{
 								TaskId:   tc.name,
-								SrcPid:   idgen.PeerID(iputils.IPv4),
-								DstPid:   idgen.PeerID(iputils.IPv4),
+								SrcPid:   idgen.PeerID(ip.IPv4),
+								DstPid:   idgen.PeerID(ip.IPv4),
 								StartNum: uint32(n),
 								Limit:    tc.limit,
 							}
diff --git a/client/daemon/storage/local_storage_test.go b/client/daemon/storage/local_storage_test.go
index e1e24e0a..17c31363 100644
--- a/client/daemon/storage/local_storage_test.go
+++ b/client/daemon/storage/local_storage_test.go
@@ -36,9 +36,9 @@ import (
 	"d7y.io/dragonfly/v2/client/daemon/test"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/internal/util"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	_ "d7y.io/dragonfly/v2/pkg/rpc/dfdaemon/server"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 func TestMain(m *testing.M) {
@@ -254,7 +254,7 @@ func TestLocalTaskStore_PutAndGetPiece(t *testing.T) {
 						return int32(len(pieces)), int64(len(testBytes)), true
 					},
 				})
-				assert.Equal(digestutils.Sha256(piecesMd5...), lts.PieceMd5Sign)
+				assert.Equal(digest.Sha256(piecesMd5...), lts.PieceMd5Sign)
 
 				// clean up test data
 				lts.lastAccess.Store(time.Now().Add(-1 * time.Hour).UnixNano())
@@ -268,7 +268,7 @@ func TestLocalTaskStore_PutAndGetPiece(t *testing.T) {
 						return int32(len(pieces)), int64(len(testBytes)), true
 					},
 				})
-				assert.Equal(digestutils.Sha256(piecesMd5...), lsts.PieceMd5Sign)
+				assert.Equal(digest.Sha256(piecesMd5...), lsts.PieceMd5Sign)
 
 				// keep original offset
 				err = lsts.Store(context.Background(),
diff --git a/client/dfget/dfget_test.go b/client/dfget/dfget_test.go
index 2fdf4e65..862e2a4d 100644
--- a/client/dfget/dfget_test.go
+++ b/client/dfget/dfget_test.go
@@ -30,10 +30,10 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/pkg/digest"
 	"d7y.io/dragonfly/v2/pkg/idgen"
 	"d7y.io/dragonfly/v2/pkg/source"
 	sourcemock "d7y.io/dragonfly/v2/pkg/source/mock"
-	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
 func Test_downloadFromSource(t *testing.T) {
@@ -53,7 +53,7 @@ func Test_downloadFromSource(t *testing.T) {
 	cfg := &config.DfgetConfig{
 		URL:    "http://a.b.c/xx",
 		Output: output,
-		Digest: strings.Join([]string{digestutils.Sha256Hash.String(), digestutils.Sha256(content)}, ":"),
+		Digest: strings.Join([]string{digest.Sha256Hash.String(), digest.Sha256(content)}, ":"),
 	}
 	request, err := source.NewRequest(cfg.URL)
 	assert.Nil(t, err)
diff --git a/pkg/util/digestutils/digest_reader_test.go b/pkg/digest/digest_reader_test.go
similarity index 92%
rename from pkg/util/digestutils/digest_reader_test.go
rename to pkg/digest/digest_reader_test.go
index b5b1d337..f05e99a5 100644
--- a/pkg/util/digestutils/digest_reader_test.go
+++ b/pkg/digest/digest_reader_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package digestutils
+package digest
 
 import (
 	"bytes"
@@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
-func TestNewDigestReader(t *testing.T) {
+func TestNewReader(t *testing.T) {
 	assert := testifyassert.New(t)
 
 	testCases := []struct {
@@ -95,7 +95,7 @@ func TestNewDigestReader(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			digest := tc.digest(tc.data)
 			buf := bytes.NewBuffer(tc.data)
-			reader, err := NewDigestReader(logger.With("test", "test"), buf, digest)
+			reader, err := NewReader(logger.With("test", "test"), buf, digest)
 			assert.Nil(err)
 			data, err := io.ReadAll(reader)
 			assert.Nil(err)
diff --git a/pkg/util/digestutils/digest_test.go b/pkg/digest/digest_test.go
similarity index 96%
rename from pkg/util/digestutils/digest_test.go
rename to pkg/digest/digest_test.go
index 442d594b..7fa499ca 100644
--- a/pkg/util/digestutils/digest_test.go
+++ b/pkg/digest/digest_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package digestutils
+package digest
 
 import (
 	"crypto/md5"
diff --git a/pkg/util/hostutils/host_utils_test.go b/pkg/net/fqdn/fqdn_test.go
similarity index 82%
rename from pkg/util/hostutils/host_utils_test.go
rename to pkg/net/fqdn/fqdn_test.go
index 503d790b..eb457c19 100644
--- a/pkg/util/hostutils/host_utils_test.go
+++ b/pkg/net/fqdn/fqdn_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package hostutils
+package fqdn
 
 import (
 	"testing"
@@ -22,11 +22,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestHostname(t *testing.T) {
-	hostname := hostname()
-	assert.NotEmpty(t, hostname)
-}
-
 func TestFQDNHostname(t *testing.T) {
 	fqdn := fqdnHostname()
 	assert.NotEmpty(t, fqdn)
diff --git a/pkg/util/net/httputils/http_utils_test.go b/pkg/net/http/http_test.go
similarity index 98%
rename from pkg/util/net/httputils/http_utils_test.go
rename to pkg/net/http/http_test.go
index 5927bf3e..06542d61 100644
--- a/pkg/util/net/httputils/http_utils_test.go
+++ b/pkg/net/http/http_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package httputils
+package http
 
 import (
 	"net/http"
diff --git a/pkg/util/rangeutils/range_test.go b/pkg/net/http/range_test.go
similarity index 97%
rename from pkg/util/rangeutils/range_test.go
rename to pkg/net/http/range_test.go
index 4da97fc5..b201ed08 100644
--- a/pkg/util/rangeutils/range_test.go
+++ b/pkg/net/http/range_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package rangeutils
+package http
 
 import (
 	"testing"
diff --git a/pkg/net/ip/ip_test.go b/pkg/net/ip/ip_test.go
new file mode 100644
index 00000000..311eec0c
--- /dev/null
+++ b/pkg/net/ip/ip_test.go
@@ -0,0 +1,29 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ip
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestExternalIPv4(t *testing.T) {
+	ip, err := externalIPv4()
+	assert.Nil(t, err)
+	assert.NotEmpty(t, ip)
+}
diff --git a/pkg/util/net/urlutils/url_utils_test.go b/pkg/net/url/url_test.go
similarity index 59%
rename from pkg/util/net/urlutils/url_utils_test.go
rename to pkg/net/url/url_test.go
index 106649fc..f309da7c 100644
--- a/pkg/util/net/urlutils/url_utils_test.go
+++ b/pkg/net/url/url_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package urlutils
+package url
 
 import (
 	"testing"
@@ -22,13 +22,14 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestIsValidURL(t *testing.T) {
-	assert.True(t, IsValidURL("http://www.x.yy"))
-	assert.True(t, IsValidURL("http://www.x.yy/path"))
-	assert.False(t, IsValidURL("http:///path"))
+func TestFilterQuery(t *testing.T) {
+	url, err := FilterQuery("http://www.xx.yy/path?u=f&x=y&m=z&x=s#size", []string{"x", "m"})
+	assert.Nil(t, err)
+	assert.Equal(t, "http://www.xx.yy/path?u=f#size", url)
 }
 
-func TestFilterURLParam(t *testing.T) {
-	url := "http://www.xx.yy/path?u=f&x=y&m=z&x=s#size"
-	assert.Equal(t, "http://www.xx.yy/path?u=f#size", FilterURLParam(url, []string{"x", "m"}))
+func TestIsValid(t *testing.T) {
+	assert.True(t, IsValid("http://www.x.yy"))
+	assert.True(t, IsValid("http://www.x.yy/path"))
+	assert.False(t, IsValid("http:///path"))
 }
diff --git a/pkg/source/clients/hdfsprotocol/hdfs_source_client_test.go b/pkg/source/clients/hdfsprotocol/hdfs_source_client_test.go
index d405cf02..ca408e70 100644
--- a/pkg/source/clients/hdfsprotocol/hdfs_source_client_test.go
+++ b/pkg/source/clients/hdfsprotocol/hdfs_source_client_test.go
@@ -31,8 +31,8 @@ import (
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 
+	"d7y.io/dragonfly/v2/pkg/net/http"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 )
 
 var sourceClient source.ResourceClient
@@ -219,7 +219,7 @@ func Test_Download_FileExist_ByRange(t *testing.T) {
 	})
 	defer patch.Reset()
 
-	rang := &rangeutils.Range{StartIndex: 0, EndIndex: uint64(hdfsExistFileContentLength) - 1}
+	rang := &http.Range{StartIndex: 0, EndIndex: uint64(hdfsExistFileContentLength) - 1}
 	// exist file
 	request, err := source.NewRequestWithHeader(hdfsExistFileURL, map[string]string{
 		source.Range: rang.String(),
@@ -242,7 +242,7 @@ func TestDownload_FileNotExist(t *testing.T) {
 
 	defer patch.Reset()
 
-	rang := rangeutils.Range{StartIndex: 0, EndIndex: uint64(hdfsExistFileContentLength)}
+	rang := http.Range{StartIndex: 0, EndIndex: uint64(hdfsExistFileContentLength)}
 
 	request, err := source.NewRequestWithHeader(hdfsNotExistFileURL, map[string]string{
 		source.Range: rang.String(),
@@ -279,7 +279,7 @@ func Test_DownloadWithResponseHeader_FileExist_ByRange(t *testing.T) {
 		return len(b), io.EOF
 	})
 
-	rang := rangeutils.Range{StartIndex: uint64(hdfsExistFileRangeStart), EndIndex: uint64(hdfsExistFileRangeEnd)}
+	rang := http.Range{StartIndex: uint64(hdfsExistFileRangeStart), EndIndex: uint64(hdfsExistFileRangeEnd)}
 	request, err := source.NewRequest(hdfsExistFileURL)
 	assert.Nil(t, err)
 	request.Header.Add(source.Range, rang.String())
@@ -297,7 +297,7 @@ func TestDownloadWithResponseHeader_FileNotExist(t *testing.T) {
 	})
 	defer patch.Reset()
 
-	rang := rangeutils.Range{StartIndex: 0, EndIndex: uint64(hdfsExistFileContentLength)}
+	rang := http.Range{StartIndex: 0, EndIndex: uint64(hdfsExistFileContentLength)}
 	request, err := source.NewRequest(hdfsNotExistFileURL)
 	assert.Nil(t, err)
 	request.Header.Add(source.Range, rang.String())
diff --git a/pkg/source/clients/httpprotocol/http_source_client_test.go b/pkg/source/clients/httpprotocol/http_source_client_test.go
index a64cb575..892330c8 100644
--- a/pkg/source/clients/httpprotocol/http_source_client_test.go
+++ b/pkg/source/clients/httpprotocol/http_source_client_test.go
@@ -29,8 +29,8 @@ import (
 	"github.com/pkg/errors"
 	"github.com/stretchr/testify/suite"
 
+	nethttp "d7y.io/dragonfly/v2/pkg/net/http"
 	"d7y.io/dragonfly/v2/pkg/source"
-	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 )
 
 func TestHTTPSourceClientTestSuite(t *testing.T) {
@@ -79,7 +79,7 @@ func (suite *HTTPSourceClientTestSuite) SetupTest() {
 
 	httpmock.RegisterResponder(http.MethodGet, normalRawURL, func(request *http.Request) (*http.Response, error) {
 		if rang := request.Header.Get(headers.Range); rang != "" {
-			r, _ := rangeutils.GetRange(rang[6:])
+			r, _ := nethttp.GetRange(rang[6:])
 			header := http.Header{}
 			header.Set(headers.LastModified, lastModified)
 			header.Set(headers.ETag, etag)
diff --git a/pkg/util/mathutils/math_utils_test.go b/pkg/strings/strings_test.go
similarity index 65%
rename from pkg/util/mathutils/math_utils_test.go
rename to pkg/strings/strings_test.go
index b42c9adf..0d1edadc 100644
--- a/pkg/util/mathutils/math_utils_test.go
+++ b/pkg/strings/strings_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package mathutils
+package strings
 
 import (
 	"testing"
@@ -22,18 +22,13 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestIsInteger(t *testing.T) {
-	assert.True(t, IsInteger("123"))
-
-	assert.True(t, IsInteger("-123"))
-
-	assert.False(t, IsInteger(""))
-
-	assert.False(t, IsInteger("x"))
+func TestIsBlank(t *testing.T) {
+	assert.True(t, IsBlank(""))
+	assert.True(t, IsBlank("	  "))
+	assert.False(t, IsBlank("x"))
 }
 
-func TestIsNatural(t *testing.T) {
-	assert.True(t, IsNatural("123"))
-
-	assert.False(t, IsNatural("-1"))
+func TestContains(t *testing.T) {
+	assert.True(t, Contains([]string{"a", "B"}, "B"))
+	assert.False(t, Contains([]string{"a", "B"}, "b"))
 }
diff --git a/pkg/util/structutils/struct_utils_test.go b/pkg/structure/structure_test.go
similarity index 96%
rename from pkg/util/structutils/struct_utils_test.go
rename to pkg/structure/structure_test.go
index 6b1606cb..a2823e72 100644
--- a/pkg/util/structutils/struct_utils_test.go
+++ b/pkg/structure/structure_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package structutils
+package structure
 
 import (
 	"testing"
diff --git a/pkg/util/timeutils/time_utils_test.go b/pkg/time/time_test.go
similarity index 96%
rename from pkg/util/timeutils/time_utils_test.go
rename to pkg/time/time_test.go
index 672cd7e0..069ceb2c 100644
--- a/pkg/util/timeutils/time_utils_test.go
+++ b/pkg/time/time_test.go
@@ -1,5 +1,5 @@
 /*
- *     Copyright 2020 The Dragonfly Authors
+ *     Copyright 2022 The Dragonfly Authors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package timeutils
+package time
 
 import (
 	"testing"
diff --git a/pkg/util/mathutils/random_test.go b/pkg/util/mathutils/random_test.go
deleted file mode 100644
index 10017c44..00000000
--- a/pkg/util/mathutils/random_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package mathutils
-
-import (
-	"fmt"
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestRandBackoff(t *testing.T) {
-	initBackOff := 0.1
-	maxBackOff := 3.0
-
-	v1 := RandBackoff(initBackOff, maxBackOff, 2, 5)
-	fmt.Printf("rand v1: %v\n", v1)
-
-	assert.True(t, v1 <= time.Duration(maxBackOff*float64(time.Second)))
-	assert.True(t, v1 >= time.Duration(initBackOff*float64(time.Second)))
-
-	v2 := RandBackoff(initBackOff, maxBackOff, 2, 5)
-	fmt.Printf("rand v2: %v\n", v2)
-
-	assert.NotEqual(t, v1, v2)
-}
diff --git a/pkg/util/net/iputils/ip_utils_test.go b/pkg/util/net/iputils/ip_utils_test.go
deleted file mode 100644
index fde86137..00000000
--- a/pkg/util/net/iputils/ip_utils_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package iputils
-
-import (
-	"net"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestExternalIPv4(t *testing.T) {
-	ip, err := externalIPv4()
-	assert.Nil(t, err)
-	assert.NotEmpty(t, ip)
-}
-
-func Test_sortIP(t *testing.T) {
-	var addrs = []net.IP{[]byte("2.3.4.5"), []byte("1.2.3.4"), []byte("3.4.5.6")}
-	sortIP(addrs)
-	assert.EqualValues(t, addrs, []net.IP{[]byte("1.2.3.4"), []byte("2.3.4.5"), []byte("3.4.5.6")})
-
-	var normalAddrs = []net.IP{[]byte("2.3.4.5"), []byte("1.2.3.4"), []byte("3.4.5.6")}
-	var preferAddrs = []net.IP{[]byte("5.3.4.5"), []byte("4.2.3.4"), []byte("6.4.5.6")}
-	sortIP(preferAddrs)
-	sortIP(normalAddrs)
-	assert.EqualValues(t, append(preferAddrs, normalAddrs...), []net.IP{[]byte("4.2.3.4"), []byte("5.3.4.5"), []byte("6.4.5.6"), []byte("1.2.3.4"),
-		[]byte("2.3.4.5"), []byte("3.4.5.6")})
-}
diff --git a/pkg/util/stringutils/string_utils_test.go b/pkg/util/stringutils/string_utils_test.go
deleted file mode 100644
index 433c8e91..00000000
--- a/pkg/util/stringutils/string_utils_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- *     Copyright 2020 The Dragonfly Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package stringutils
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestSubString(t *testing.T) {
-	var str = "hello,world"
-
-	assert.Equal(t, SubString(str, 0, 5), "hello")
-
-	assert.Equal(t, SubString(str, 1, 5), "ello")
-
-	assert.Equal(t, SubString(str, 1, 1), "")
-}
-
-func TestIsBlank(t *testing.T) {
-	assert.True(t, IsBlank(""))
-
-	assert.True(t, IsBlank("	  "))
-
-	assert.False(t, IsBlank("x"))
-}
-
-func TestIsEmpty(t *testing.T) {
-	assert.True(t, IsEmpty(""))
-
-	assert.False(t, IsEmpty(" "))
-}
-
-func TestContains(t *testing.T) {
-	assert.True(t, ContainsFold([]string{"a", "B"}, "b"))
-	assert.False(t, Contains([]string{"a", "B"}, "b"))
-}
-
-func TestRandString(t *testing.T) {
-	assert.True(t, len(RandString(10)) == 10)
-	x := RandString(10)
-	y := RandString(10)
-	assert.False(t, x == y)
-}
diff --git a/scheduler/config/config_test.go b/scheduler/config/config_test.go
index 3161ff3f..baa976d2 100644
--- a/scheduler/config/config_test.go
+++ b/scheduler/config/config_test.go
@@ -25,8 +25,8 @@ import (
 	testifyassert "github.com/stretchr/testify/assert"
 	"gopkg.in/yaml.v3"
 
-	"d7y.io/dragonfly/v2/pkg/util/hostutils"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
+	"d7y.io/dragonfly/v2/pkg/net/fqdn"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/scheduler/storage"
 )
 
@@ -120,8 +120,8 @@ func TestConfig_New(t *testing.T) {
 
 	assert.EqualValues(config, &Config{
 		Server: &ServerConfig{
-			IP:     iputils.IPv4,
-			Host:   hostutils.FQDNHostname,
+			IP:     ip.IPv4,
+			Host:   fqdn.FQDNHostname,
 			Listen: "0.0.0.0",
 			Port:   8002,
 		},
diff --git a/test/e2e/manager/preheat.go b/test/e2e/manager/preheat.go
index 52d8b389..686f2ce4 100644
--- a/test/e2e/manager/preheat.go
+++ b/test/e2e/manager/preheat.go
@@ -33,7 +33,7 @@ import (
 	"d7y.io/dragonfly/v2/manager/types"
 	"d7y.io/dragonfly/v2/pkg/idgen"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
-	"d7y.io/dragonfly/v2/pkg/util/structutils"
+	"d7y.io/dragonfly/v2/pkg/structure"
 	"d7y.io/dragonfly/v2/test/e2e/e2eutil"
 )
 
@@ -57,7 +57,7 @@ var _ = Describe("Preheat with manager", func() {
 				sha256sum1 := strings.Split(string(out), " ")[0]
 
 				// preheat file
-				req, err := structutils.StructToMap(types.CreatePreheatJobRequest{
+				req, err := structure.StructToMap(types.CreatePreheatJobRequest{
 					Type: internaljob.PreheatJob,
 					Args: types.PreheatArgs{
 						Type: "file",
@@ -110,7 +110,7 @@ var _ = Describe("Preheat with manager", func() {
 			fsPod := getFileServerExec()
 
 			// preheat file
-			req, err := structutils.StructToMap(types.CreatePreheatJobRequest{
+			req, err := structure.StructToMap(types.CreatePreheatJobRequest{
 				Type: internaljob.PreheatJob,
 				Args: types.PreheatArgs{
 					Type: "image",
diff --git a/test/tools/stress/main.go b/test/tools/stress/main.go
index 4bb1c433..742863a7 100644
--- a/test/tools/stress/main.go
+++ b/test/tools/stress/main.go
@@ -37,8 +37,8 @@ import (
 	"github.com/montanaflynn/stats"
 
 	"d7y.io/dragonfly/v2/client/config"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 	"d7y.io/dragonfly/v2/pkg/unit"
-	"d7y.io/dragonfly/v2/pkg/util/net/iputils"
 )
 
 var (
@@ -128,7 +128,7 @@ loop:
 }
 
 func debug() {
-	debugAddr := fmt.Sprintf("%s:%d", iputils.IPv4, 18066)
+	debugAddr := fmt.Sprintf("%s:%d", ip.IPv4, 18066)
 	viewer.SetConfiguration(viewer.WithAddr(debugAddr))
 	if err := statsview.New().Start(); err != nil {
 		log.Println("stat view start failed", err)