HTTP сваляч

Предадени решения

Краен срок:
03.01.2017 16:30
Точки:
10

Срокът за предаване на решения е отминал

В тази задача ще трябва да имплементирате сваляне на файл. Файлът го има дублиран на множество URL-и и ще трябва да разпределите свалянето му по всички URL-и. Това означава различни парчета на файлът да бъдат свалени от различните URL-и.

func DownloadFile(ctx context.Context, urls []string) io.Reader

Функцията трябва да върне io.Reader-а независимо от правенето на заявките към отделните url-и.

URLs

По подразбиране се сваля от всички URL-и, като в даден момент се прави по една заявка към всеки един от тях. Реда не е важен, но трябва да се балансира количеството свалени байтове. Това означава, че ако има два URL-а, валидна имплементация е да се прочетaт първите 10% байтове от първия URL вторите 10% от втория, следващите от първия и т.н. Или първите 50% от единия и вторите 50% от втория. Важното е свалените байтове да са равномерно разпределени. Например, не би било валидно свалените байтове да са 20% от първия и 80% от втория. Или 100% да са от само един URL.

Абсолютно точно разпределение по свалените байтове не винаги ще е възможно. Допустима е по един байт разлика между различните URL-и. Пример: файл, който е 100 байта и трябва да бъде свален от 3 URL-а. Тогава е напълно валидно от два да бъдат свалени по 33 байта и от един - 34 байта.

Прекъсната връзка (т.е. невърнати всички поискани байтове) от даден сървър не се счита за грешка и трябва отново да се опитате да се свържете към този сървър. При грешка при самото свързване обаче (timeout, статус 4xx или 5xx) обаче, трябва да считате сървъра за невалиден и да го игнорирате. Байтовете които е трябвало да се свалят от него трябва да се разпределят по равно на останалите.

Ако всички URL-и върнат грешки, то трябва върнатия Reader да върне грешка с текст no valid urls.

ctx

Трябва да спрете когато Done канала за подаденият Context бъде затворен. Това включва и спиране на всички заявки, които към момента са активни, както и връщане на каквотo е прочетено до сега. Ако context-а бъде затворен преди да се досвали всичко, то грешката на последния Read от io.Reader-а да е грешката от context-а. Ако вече са завършили всички заявки и само се чете от io.Reader-а, то тогава да се счита че всичко е минало нормално.

Подаването на nil Context да се приема за нормална ситуация, в която просто няма да може да се спира свалянето отвън.

Ако Контекста има стойност за ключ "max-connections", то тя ще е int и трябва да се прилага като максимален брой конкуренти кънекции. Пример: 10 URL-a и max-connections 2. Тогава отново трябва да се разпределята байтовете по всичките сървъри, но максималният брой активни връзки към тях трябва да бъде 2. По подразбиране (когато ключа не съществува) няма лимит.

Резултата io.Reader

Върнатия io.Reader трябва да чете последователно върнатия резултат. След като свърши, трябва да върне грешка io.EOF. Прочетете https://golang.org/pkg/io/#Reader за повече информация как трябва да работи.

Върнатия io.Reader винаги трябва да прочита данните, които са били върнати, в последователността в която са във файла. Трябва да се върната максималното количество такива байтове дори и при грешка.

Пример: Има два URL-a за файл 60 байта, от които са били прочетени първите 20 байта от URL[1] и следващите 10 от URL[0] след което той затваря и започва да връща грешка. Вече обаче се свалят следващите 20 байта от URL[1], тези с индекси 40 до 59. След като завършат се доизтеглят 10-те байта, които не са успели през URL[0], но след като се върнат 3 байта връзката се затваря от другия край и на следващата заявка сървъра връща грешка. Повече заявки няма да се правят и ще бъде върната грешка. В този момент от io.Reader се изчитат всичките байтове в тяхната правилна последователност. тоест първо 20 байта от URL[1], после 10-те взети от URL[0] преди да спре да връща и после трите които са били доизчетени от URL[1], като след това се връща грешка че всички сървъри връщат грешки(виж по долу). 20-те байта които са прочетени от URL[1] с индекси 40-59 не се връщат.

В края на изпълнението след като всичко бъде прочетено от io.Reader и той върне грешка (включително io.EOF) не трябва да има останали висящи goroutine-и.

Забележки/Препоръки:

  • Ще е хубаво да прочетете как се правят Range заявки и да ги използвате - https://tools.ietf.org/html/rfc7233
  • URL-ите винаги ще бъдат валидни такива и няма нужда да ги преправяте по някакъв начин - ще бъдат във вида http://127.0.0.3:8082/path/to/awesome/file.pdf или нещо подобно.
  • io.Reader-а е за предпочитане никога да не връща 0, nil - лоша практика е.
  • Навярно ще искате да правите максимално големите range заявки с които все още изпълнявате останлата част от условието, вместо множество мини заявки.
  • Преизползвайте кънекции с http.Client & co.
  • Приемете че сървърите винаги отговарят на range заявки и то със правилния range. Също така отговарят и на HEAD заявки.
  • Приема се че URL-ите са за едни и същи файлове и няма нужда да го проверявате по някакъв начин