Язык Go реализует файловую систему WebDAV

Go HTTP Язык программирования

WebDAV (распределенное создание и управление версиями через Интернет) — это протокол связи, основанный на протоколе HTTP 1.1. Он расширяет HTTP 1.1 и добавляет некоторые новые методы в дополнение к нескольким стандартным методам HTTP, таким как GET, POST, HEAD и т. д., чтобы приложения могли напрямую читать и записывать на веб-сервер, а также поддерживать блокировку и разблокировку файлов при записи. (Unlock) , а также может поддерживать контроль версий файлов.

Использовать WebDAV может быть сделано:

- Обработка свойств (метаданных). Вы можете использовать методы PROPFIND и PROPPATCH в WebDAV для создания, удаления и запроса информации о файлах, такой как автор и дата создания.

- Управление коллекциями и ресурсами. Вы можете использовать методы GET, PUT, DELETE и MKCOL для создания коллекций документов и получения списка элементов иерархии (аналогично каталогам в файловой системе).

- Заперто. Одновременная работа над документом может быть запрещена нескольким людям. Это поможет предотвратить проблемы «потерянных обновлений» (изменения перезаписываются).

- Операции с пространством имен. Вы можете использовать методы COPY и MOVE, чтобы сервер копировал и удалял связанные ресурсы.

В настоящее время обычные NAS предоставляют сервисную функцию WebDAV, и многие приложения для мобильных телефонов также реализуют обмен файлами между приложениями через протокол WebDAV. Чтобы предоставить собственную службу WebDAV, необходимо сначала установить соответствующее программное обеспечение. Бесплатное программное обеспечение WebDAVNav Server можно установить из App Store под macOS. Интерфейс запуска службы WebDAVNav Server выглядит следующим образом:

В этом разделе мы пытаемся реализовать собственный сервис WebDAV на языке Go.

## Расширение WebDAV для HTTP

WebDAV расширяет протокол HTTP/1.1. Он определяет новые заголовки HTTP, через которые клиенты могут передавать запросы ресурсов, специфичные для WebDAV. Эти заголовки:

- Destination:

- Lock-Token:

- Timeout:

- DAV:

- If:

- Depth:

- Overwrite:

В то же время стандарт WebDAV также вводит несколько новых HTTP-методов для указания серверам с поддержкой WebDAV, как обрабатывать запросы. Эти методы дополняют существующие методы, такие как GET, PUT и DELETE, и могут использоваться для выполнения транзакций WebDAV. Вот введение в эти новые методы HTTP:

- ЗАМОК. Чтобы заблокировать ресурсы, используйте заголовок Lock-Token:.

- РАЗБЛОКИРОВАТЬ. Для разблокировки используйте заголовок Lock-Token:.

- ПРОПАТЧ. Установите, измените или удалите свойства одного ресурса.

- ПРОПФИНД. Используется для получения информации об одном или нескольких свойствах одного или нескольких ресурсов. Запрос может содержать заголовок Depth: со значением 0, 1 или бесконечность. Среди них 0 указывает, что атрибут коллекции по указанному URI (то есть файл или каталог) будет получен; 1 указывает, что атрибут коллекции и ресурсы непосредственно ниже указанного URI (невложенные подкаталоги) или подфайлы); бесконечность означает, что будут загружены все подкаталоги или подфайлы (слишком большая глубина увеличит нагрузку на сервер).

- КОПИРОВАТЬ. Чтобы скопировать ресурс, вы можете использовать заголовок Depth: для перемещения ресурса и заголовок Destination: для указания места назначения. Метод COPY также использует заголовок Overwrite:, если это необходимо.

- ПЕРЕЕХАТЬ. Чтобы переместить ресурс, вы можете использовать заголовок Depth: для перемещения ресурса и заголовок Destination: для указания пункта назначения. Метод MOVE также использует заголовок Overwrite:, если это необходимо.

- МККОЛ. Используется для создания новой коллекции (соответствующей каталогу).

## Самый простой сервис WebDAV

Пакет расширения Go `golang.org/x/net/webdav` обеспечивает поддержку служб WebDAV. Среди них webdav.Handler реализует интерфейс http.Handle, который используется для обработки специфичных для WebDAV http-запросов. Чтобы создать объект webdav.Handler, нам нужно указать как минимум файловую систему и службу блокировки. Среди них webdav.Dir сопоставляет локальную файловую систему с файловой системой WebDAV, а webdav.NewMemLS создает службу блокировки на основе локальной памяти.

Ниже приведена простейшая реализация службы WebDAV:

```go

package main

import (

    "net/http"

    "golang.org/x/net/webdav"

)

func main() {

    http.ListenAndServe(":8080", &webdav.Handler{

        FileSystem: webdav.Dir("."),

        LockSystem: webdav.NewMemLS(),

    })

}

```

После запуска к текущему каталогу можно получить доступ через WebDAV.

## Услуги WebDAV только для чтения WebDAV

Реализованная выше служба WebDAV по умолчанию может обращаться к файловой системе без пароля, добавлять, изменять и удалять файлы может любой анонимный пользователь, что слишком небезопасно для сетевых служб.

Чтобы предотвратить случайное или злонамеренное изменение пользователями, мы можем отключить функцию изменения WebDAV. Что касается спецификации протокола WebDAV, операции, связанные с модификацией, в основном включают несколько методов, таких как PUT/DELETE/PROPPATCH/MKCOL/COPY/MOVE. Мы можем реализовать службу WebDAV только для чтения, защитив эти методы.

```go

func main() {

    fs := &webdav.Handler{

        FileSystem: webdav.Dir("."),

        LockSystem: webdav.NewMemLS(),

    }

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {

        switch req.Method {

        case "PUT", "DELETE", "PROPPATCH", "MKCOL", "COPY", "MOVE":

            http.Error(w, "WebDAV: Read Only!!!", http.StatusForbidden)

            return

        }

        fs.ServeHTTP(w, req)

    })

    http.ListenAndServe(":8080", nil)

}

```

Мы переупаковываем метод fs.ServeHTTP с помощью http.HandleFunc, а затем блокируем операции, связанные с обновлением. Таким образом, мы реализовали службу WebDAV только для чтения.

## Служба аутентификации по паролю WebDAV

WebDAV — это стандарт, основанный на расширении протокола HTTP, мы можем установить имя пользователя и пароль через базовый механизм аутентификации HTTP.

```go

func main() {

    fs := &webdav.Handler{

        FileSystem: webdav.Dir("."),

        LockSystem: webdav.NewMemLS(),

    }

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {

// Получить имя пользователя / пароль

        username, password, ok := req.BasicAuth()

        if !ok {

            w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)

            w.WriteHeader(http.StatusUnauthorized)

            return

        }

// Подтвердить имя пользователя/пароль

        if username != "user" || password != "123456" {

            http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)

            return

        }

        fs.ServeHTTP(w, req)

    })

    http.ListenAndServe(":8080", nil)

}

```

Мы используем req.BasicAuth, чтобы получить имя пользователя и пароль, а затем пройти аутентификацию. Если имя пользователя и пароль не установлены, будет возвращено состояние http.StatusUnauthorized, и HTTP-клиент откроет окно для ввода пароля пользователем.

Поскольку протокол HTTP не зашифрован, имя пользователя и пароль также передаются в открытом виде. Для большей безопасности мы можем предоставить службу WebDAV с протоколом HTTPS. Для этого нам нужно подготовить файл сертификата (программа generate_cert.go в пакете crypto/tls может сгенерировать сертификат), а затем использовать http.ListenAndServeTLS для запуска сервиса https.

В то же время следует отметить, что начиная с Windows Vista Microsoft отключила базовую форму аутентификации WebDAV в виде http (KB841215), а соединение https должно использоваться по умолчанию. Вы можете изменить реестр в Windows Vista/7/8:

    HKEY_LOCAL_MACHINE>>SYSTEM>>CurrentControlSet>>Services>>WebClient>>Parameters>>BasicAuthLevel

Измените это значение с 1 на 2, затем перейдите в Панель управления/Службы и перезапустите службу WebClient.

## просмотр в браузере

WebDAV основан на протоколе HTTP и теоретически легче получить доступ к серверу WebDav из браузера. Однако после доступа к корневому каталогу службы WebDAV в браузере мы получили сообщение об ошибке «Метод не допускается».

Это связано с тем, что, согласно спецификации протокола WebDAV, метод GET для http можно использовать только для получения файлов. В библиотеке webdav, реализованной на языке Go, если вы используете GET для доступа к каталогу, будет возвращен код состояния http.StatusMethodNotAllowed, соответствующий сообщению об ошибке «Метод не разрешен».

Чтобы браузер мог удалить список каталогов, мы создаем отдельную html-страницу для операции GET для каталога:

```go

func main() {

    fs := &webdav.Handler{

        FileSystem: webdav.Dir("."),

        LockSystem: webdav.NewMemLS(),

    }

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {

        if req.Method == "GET" && handleDirList(fs.FileSystem, w, req) {

            return

        }

        fs.ServeHTTP(w, req)

    })

    http.ListenAndServe(":8080", nil)

}

```

Среди них функция handleDirList используется для обработки списка каталогов, а затем возвращает значение true. Реализация handleDirList выглядит следующим образом:

```go

func handleDirList(fs webdav.FileSystem, w http.ResponseWriter, req *http.Request) bool {

    ctx := context.Background()

    f, err := fs.OpenFile(ctx, req.URL.Path, os.O_RDONLY, 0)

    if err != nil {

        return false

    }

    defer f.Close()

    if fi, _ := f.Stat(); fi != nil && !fi.IsDir() {

        return false

    }

    dirs, err := f.Readdir(-1)

    if err != nil {

        log.Print(w, "Error reading directory", http.StatusInternalServerError)

        return false

    }

    w.Header().Set("Content-Type", "text/html; charset=utf-8")

    fmt.Fprintf(w, "<pre>\n")

    for _, d := range dirs {

        name := d.Name()

        if d.IsDir() {

            name += "/"

        }

        fmt.Fprintf(w, "<a href=\"%s\" >%s</a>\n", name, name)

    }

    fmt.Fprintf(w, "</pre>\n")

    return true

}

```

Доступ к списку каталогов WebDAV теперь можно получить через браузер.

## Практический сервис WebDAV

Чтобы создать практичный сервис WebDAV, мы устанавливаем соответствующую информацию с помощью параметров командной строки и в то же время интегрируем предыдущие функции.

```go

package main

import (

    "flag"

    "fmt"

    "log"

    "net/http"

    "os"

    "golang.org/x/net/context"

    "golang.org/x/net/webdav"

)

var (

    flagRootDir   = flag.String("dir", "", "webdav root dir")

    flagHttpAddr  = flag.String("http", ":80", "http or https address")

    flagHttpsMode = flag.Bool("https-mode", false, "use https mode")

    flagCertFile  = flag.String("https-cert-file", "cert.pem", "https cert file")

    flagKeyFile   = flag.String("https-key-file", "key.pem", "https key file")

    flagUserName  = flag.String("user", "", "user name")

    flagPassword  = flag.String("password", "", "user password")

    flagReadonly  = flag.Bool("read-only", false, "read only")

)

func init() {

    flag.Usage = func() {

        fmt.Fprintf(os.Stderr, "Usage of WebDAV Server\n")

        flag.PrintDefaults()

        fmt.Fprintf(os.Stderr, "\nReport bugs to <chaishushan{AT}gmail.com>. \n")

    }

}

func main() {

    flag.Parse()

    fs := &webdav.Handler{

        FileSystem: webdav.Dir(*flagRootDir),

        LockSystem: webdav.NewMemLS(),

    }

    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {

        if *flagUserName != "" && *flagPassword != "" {

            username, password, ok := req.BasicAuth()

            if !ok {

                w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)

                w.WriteHeader(http.StatusUnauthorized)

                return

            }

            if username != *flagUserName || password != *flagPassword {

                http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)

                return

            }

        }

        if req.Method == "GET" && handleDirList(fs.FileSystem, w, req) {

            return

        }

        if *flagReadonly {

            switch req.Method {

            case "PUT", "DELETE", "PROPPATCH", "MKCOL", "COPY", "MOVE":

                http.Error(w, "WebDAV: Read Only!!!", http.StatusForbidden)

                return

            }

        }

        fs.ServeHTTP(w, req)

    })

    if *flagHttpsMode {

        http.ListenAndServeTLS(*flagHttpAddr, *flagCertFile, *flagKeyFile, nil)

    } else {

        http.ListenAndServe(*flagHttpAddr, nil)

    }

}

func handleDirList(fs webdav.FileSystem, w http.ResponseWriter, req *http.Request) bool {

// ссылка на предыдущий код

}

```

Показать справочную информацию:

```

go run main.go -h

Usage of WebDAV Server

-dir string

    webdav root dir

-http string

    http or https address (default ":80")

-https-cert-file string

    https cert file (default "cert.pem")

-https-key-file string

    https key file (default "key.pem")

-https-mode

    use https mode

-password string

    user password

-read-only

    read only

-user string

    user name

Report bugs to <chaishushan{AT}gmail.com>.

```

Следующая команда запускает службу WebDAV с помощью Https, соответствующего локальному каталогу установки языка Go, и одновременно устанавливает имя пользователя и пароль:

```

go run main.go -https-mode -user=user -password=123456 -dir=/usr/local/go

```

Ниже приведен пример доступа к /usr/local/go по протоколу WebDAV через приложение WebDANNav+ на iPod:

Для получения дополнительных статей, пожалуйста, посетите: https://chai2010.cn