One place for hosting & domains

      Функция init в Go


      Введение

      В Go заданнная функция init() выделяет элемент кода, который запускатся до любой другой части вашего пакета. Этот код запускается сразу же после импорта пакета, и его можно использовать при необходимости инициализации приложения в определенном состоянии, например, если для запуска приложения требуется определенная конфигурация или набор ресурсов. Также используется при импорте побочных эффектов, то есть при применении методики установки состояния программы посредством импорта определенного пакета. Часто используется для регистрации одного пакета в другом, чтобы программа рассматривала правильный код для этой задачи.

      Хотя init() представляет собой полезный инструмент, иногда он делает код менее удобочитаемым, поскольку трудный в поиске экземпляр init() серьезно влияет на порядок выполнения кода. В связи с этим, начинающим разработчикам Go важно понимать все аспекты этой функции, чтобы они использовали init() в читаемой форме при написании кода.

      В этом обучающем руководстве вы узнаете, как init() используется для настройки и инициализации переменных определенных пакетов, разовых вычислений и регистрации пакетов для использования внутри других пакетов.

      Предварительные требования

      Для некоторых из приведенных в этой статье примеров вам потребуется следующее:

      .
      ├── bin
      │
      └── src
          └── github.com
              └── gopherguides
      

      Декларирование init()

      Каждый раз, когда вы декларируете функцию init(), Go загружает и запускает ее прежде всех остальных элементов этого пакета. Чтобы продемонстрировать это, в данном разделе мы подробно покажем определение функции init() и ее влияние на выполнение пакета.

      Вначале рассмотрим следующий пример кода без функции init():

      main.go

      package main
      
      import "fmt"
      
      var weekday string
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      В этой программе мы декларировали глобальную переменную с именем weekday. По умолчанию значение weekday представляет собой пустую строку.

      Запустим этот код:

      Поскольку значение weekday пустое, при запуске программы мы увидим следующее:

      Output

      Today is

      Мы можем заполнить пустую переменную, используя функцию init() для инициализации значения weekday как текущего дня. Добавьте следующие выделенные строки в файл main.go:

      main.go

      package main
      
      import (
          "fmt"
          "time"
      )
      
      var weekday string
      
      func init() {
          weekday = time.Now().Weekday().String()
      }
      
      func main() {
          fmt.Printf("Today is %s", weekday)
      }
      

      В этом коде мы импортировали и использовали пакет time для получения текущего дня недели (Now(). Weekday(). String()), а затем использовали init() для инициализации weekday с этим значением.

      Теперь при запуске программы она выводит текущий день недели:

      Output

      Today is Monday

      Хотя это показывает принцип работы функции init(), гораздо чаще init() используется при импорте пакета. Это может быть полезно, если вам требуется выполнить в пакете определенные задачи по настройке, прежде чем использовать этот пакет. Чтобы продемонстрировать это, создадим программу, которая потребует определенной инициализации для обеспечения требуемой работы пакета.

      Инициализация пакетов при импорте

      Вначале мы напишем код, который выбирает случайное существо из среза и выводит его. Однако мы не будем использовать init()в начальной программе. Это лучше покажет стоящую перед нами проблему и возможность ее решения с помощью функции init().

      Создайте в каталоге src/github.com/gopherguides/ папку с именем creature с помощью следующей команды:

      Создайте в папке creature файл с именем creature.go:

      • nano creature/creature.go

      Добавьте в этот файл следующее содержание:

      creature.go

      package creature
      
      import (
          "math/rand"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Этот файл определяет переменную с именем creatures, для которой в качестве значений инициализирован набор морских существ. Также она имеет экспортируемую функцию Random, которая выводит случайное значение переменной creatures.

      Сохраните и закройте этот файл.

      Теперь создайте пакет cmd, который мы используем для записи функции main() и вызова пакета creature.

      На том же уровне файла, где мы создали папку creature, создайте папку cmd с помощью следующей команды:

      Создайте в папке cmd файл с именем main.go:

      Добавьте в файл следующие строчки:

      cmd/main.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/creature"
      )
      
      func main() {
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
          fmt.Println(creature.Random())
      }
      

      Здесь мы импортировали пакет creature и использовали в функции main() функцию creature.Random(), чтобы получить случайное существо и вывести его четыре раза.

      Сохранение и выход из main.go.

      Теперь у нас написана вся программа. Однако перед запуском этой программы нам также потребуется создать несколько файлов конфигурации, чтобы обеспечить правильную работу нашего кода. Go использует Go Modules для настройки зависимостей пакетов при импорте ресурсов. Эти модули представляют собой файлы конфигурации в каталоге вашего пакета, которые указывают компилятору, откуда импортировать пакеты. Хотя изучение модулей не входит в состав настоящей статьи, мы можем написать несколько строк конфигурации, чтобы этот пример работал локально.

      Создайте в каталоге cmd файл с именем go.mod:

      После открытия файла добавьте в него следующий код:

      cmd/go.mod

      module github.com/gopherguides/cmd
       replace github.com/gopherguides/creature => ../creature
      

      Первая строка файла указывает компилятору, что созданный нами пакет cmd на самом деле представляет собой пакет github.com/gopherguides/cmd. Вторая строка указывает компилятору, что каталог github.com/gopherguides/creature можно найти на локальном диске в каталоге ../creature.

      Сохраните и закройте файл. Затем создайте файл go.mod в каталоге creature:

      Добавьте в файл следующую строчку кода:

      creature/go.mod

       module github.com/gopherguides/creature
      

      Это говорит компилятору, что созданный нами пакет creature на самом деле является пакетом github.com/gopherguides/creature. Без этого пакет cmd не будет знать, откуда импортировать этот пакет.

      Сохраните и закройте файл.

      Теперь у вас должны быть следующая структура каталогов и расположение файлов:

      ├── cmd
      │   ├── go.mod
      │   └── main.go
      └── creature
          ├── go.mod
          └── creature.go
      

      Мы завершили настройку и теперь можем запустить программу main с помощью следующей команды:

      Это даст нам следующее:

      Output

      jellyfish squid squid dolphin

      При запуске этой программы мы получили четыре значения и вывели их. Если мы запустим программу несколько раз, результаты всегда будут одинаковыми, хотя ожидается случайный результат. Это связано с тем, что пакет rand создает псевдослучайные числа, постоянно дающие один и тот же результат для одного и того же начального состояния. Чтобы получить действительно случайное число, мы можем задать начальное случайное число для пакета или задать изменяющийся источник, чтобы при каждом запуске программы состояние было разным. В Go обычно используется текущее время в качестве начального случайного числа для пакета rand.

      Поскольку нам нужно, чтобы пакет creature работал с функцией случайных чисел, откроем этот файл:

      • nano creature/creature.go

      Добавьте в файл creature.go следующие выделенные строки:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func Random() string {
          rand.Seed(time.Now().UnixNano())
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      В этом коде мы импортировали пакет time и использовали Seed() для использования текущего времени в качестве начального случайного числа. Сохраните и закройте файл.

      Теперь при запуске программы мы будем получать действительно случайный результат:

      Output

      jellyfish octopus shark jellyfish

      При каждом запуске программы результаты будут оставаться случайными. Однако эта реализация кода также не идеальна, поскольку при каждом вызове creature.Random() повторно задается начальное случайное число для пакета rand посредством вызова функции rand.Seed(time.Now(). UnixNano()) еще раз. Повторное начальное случайное число может совпадать с предыдущим, если время на внутренних часах не изменилось. Это может вызвать повторы шаблонов случайных чисел или увеличение времени обработки в связи с ожиданием смены времени на часах.

      Для решения этой проблемы мы можем использовать функцию init(). Обновим файл creature.go:

      • nano creature/creature.go

      Добавьте следующие строчки кода:

      creature/creature.go

      package creature
      
      import (
          "math/rand"
          "time"
      )
      
      var creatures = []string{"shark", "jellyfish", "squid", "octopus", "dolphin"}
      
      func init() {
          rand.Seed(time.Now().UnixNano())
      }
      
      func Random() string {
          i := rand.Intn(len(creatures))
          return creatures[i]
      }
      

      Добавление функции init() указывает компилятору, что при импорте пакета creature необходимо запустить функцию init() один раз и задать начальное случайное число для генерирования случайных чисел. Так нам не придется лишний раз выполнять код. Если мы запустим программу, мы по-прежнему будем получать случайные результаты:

      Output

      dolphin squid dolphin octopus

      В этом разделе мы показали, как использование функции init() может обеспечить проведение правильных расчетов или операций инициализации перед использованием пакета. Далее мы рассморим использование нескольких выражений init() в пакете.

      Использование нескольких экземпляров init()

      В отличие от функции main(), которую можно декларировать только один раз, функцию init() можно декларировать в пакете много раз. Однако при использовании нескольких экземпляров функции init() может быть сложно понять, какой из них имеет приоритет перед другими. В этом разделе мы покажем, как контролировать несколько выражений init().

      В большинстве случаев функции init() выполняются в том порядке, в каком они содержатся в программе. Рассмотрим в качестве примера следующий код:

      main.go

      package main
      
      import "fmt"
      
      func init() {
          fmt.Println("First init")
      }
      
      func init() {
          fmt.Println("Second init")
      }
      
      func init() {
          fmt.Println("Third init")
      }
      
      func init() {
          fmt.Println("Fourth init")
      }
      
      func main() {}
      

      Если мы запустим программу с помощью следующей команды:

      Мы получим следующий результат:

      Output

      First init Second init Third init Fourth init

      Обратите внимание, что каждое выражение init() выполняется в том порядке, в каком оно поступает в компилятор. Однако порядок вызова функций init() может быть не так легко определить.

      Рассмотрим более сложную структуру пакета, где у нас имеется несколько файлов, для каждого из которых декларирована собственная функция init(). Для иллюстрации мы создадим программу, передающую переменную с именем message и выводящую ее.

      Удалите каталоги creature и cmd и их содержимое из предыдущего раздела и замените их следующими каталогами и структурой файлов:

      ├── cmd
      │   ├── a.go
      │   ├── b.go
      │   └── main.go
      └── message
          └── message.go
      

      Теперь добавим содержимое каждого файла. В файле a.go добавьте следующие строчки:

      cmd/a.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          fmt.Println("a ->", message.Message)
      }
      

      Этот файл содержит одну функцию init(), которая выводит значение message.Message из пакета message.

      Добавьте следующие строки в файл b.go:

      cmd/b.go

      package main
      
      import (
          "fmt"
      
          "github.com/gopherguides/message"
      )
      
      func init() {
          message.Message = "Hello"
          fmt.Println("b ->", message.Message)
      }
      

      В файле b.go имеется одна функция init(), которая задает для message.Message значение Hello и выводит его.

      Создадим файл main.go, который будет выглядеть следующим образом:

      cmd/main.go

      package main
      
      func main() {}
      

      Этот файл ничего не делает, но предоставляет начальную точку для запуска программы.

      В заключение создайте файл message.go как показано здесь:

      message/message.go

      package message
      
      var Message string
      

      Наш пакет message декларирует экспортированную переменную Message.

      Для запуска программы выполните следующую команду в каталоге cmd:

      Поскольку в папке cmd имеется несколько файлов Go, составляющих пакет main, нам нужно указать компилятору, что все файлы .go в папке cmd должны быть скомпилированы. Использование *.go указывает компилятору на необходимость загрузить все файлы из папки cmd, которые заканчиваются на .go. Если мы отправим команду go run main.go, программа не будет компилироваться, поскольку она не увидит код в файлах a.go и b.go.

      Результат будет выглядеть следующим образом:

      Output

      a -> b -> Hello

      Согласно спецификации инициализации пакетов в языке Go, при наличии в пакете нескольких файлов они обрабатываются в алфавитном порядке. В связи с этим, когда мы первый раз распечатали message.Message из a.go, значение было пустым. Значение не было инициализировано до запуска функции init() из b.go.

      Если бы мы изменили имя файла с a.go на c.go, результат был бы другим:

      Output

      b -> Hello a -> Hello

      Теперь компилятор вначале получает b.go и значение message.Message уже инициализировано как Hello при появлении функции init() в c.go.

      Такое поведение может вызвать проблемы при выполнении кода. При разработке программного обеспечения имена файлов часто меняются, и, в связи с особенностями функции init(), изменение имен файлов может изменить последовательность обработки функций init(). Это может привести к нежелательному изменению выводимых программой результатов. Чтобы обеспечить стабильное поведение при инициализации, рекомендуется при сборке указывать компилятору несколько файлов из одного пакета в алфавитном порядке. Чтобы обеспечить загрузку всех функций init() по порядку, можно декларировать все эти функции в одном файле. Это предотвратит изменение порядка даже в случае изменения имен файлов.

      Помимо обеспечения порядка выполнения функций init(), вам также следует избегать управления состояниями в пакете с помощью глобальных переменных, т. е. переменных, которые доступны во всем пакете. В предыдущей программе переменная message.Message была доступна всему пакету и поддерживала состояние программы. В связи с таким доступом, выражения init() могли изменять переменную и снижать прогнозируемость работы программы. Чтобы избежать этого, попробуйте работать с переменными в контролируемых пространствах с минимальным доступом, обеспечивающим возможность работы программы.

      Итак, в одном пакете может быть несколько деклараций init(). Однако это может создать нежелательные эффекты и сделать программу более сложной для чтения или осложнить прогнозирование ее работы. Если не использовать несколько выражений init() или объединять их в одном файле, поведение программы не изменится в случае перемещения файлов или смены имен файлов.

      Теперь посмотрим, как функция init() используется для импорта с побочными эффектами.

      Использование init() для побочных эффектов

      Иногда в Go требуется импортировать пакет не ради его содержимого, но ради побочных эффектов, возникающих при импорте пакета. Часто это означает, что в импортируемом коде содержится выражение init(), выполняемое перед любым другим кодом, что позволяет разработчику изменять состояние программы при запуске. Такая методика называется импортированием для побочного эффекта.

      Импортирование для побочного эффекта обычно используется для функции регистрации в коде, чтобы пакет знал, какую часть кода нужно использовать вашей программе. Например, в пакете imageфункция image.Decode должна знать, какой формат она пытается декодировать (jpg, png, gif и т. д.), прежде чем ее можно будет выполнить. Для этого можно предварительно импортировать определенную программу с побочным эффектом выражения init().

      Допустим, вы пытаетесь использовать image.Decode в файле .png со следующим фрагментом кода:

      Sample Decoding Snippet

      . . .
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      . . .
      

      Программа с этим кодом будет скомпилирована, однако при попытке декодирования изображения png мы получим сообщение об ошибке.

      Для устранения этой проблемы нужно предварительно зарегистрировать формат изображения для image.Decode. К счастью, пакет image/png содержит следующее выражение init():

      image/png/reader.go

      func init() {
          image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
      }
      

      Поэтому, если мы импортируем image/png в сниппет для декодировки, функция image.RegisterFormat() пакета image/png будет запущена до любого нашего кода:

      Sample Decoding Snippet

      . . .
      import _ "image/png"
      . . .
      
      func decode(reader io.Reader) image.Rectangle {
          m, _, err := image.Decode(reader)
          if err != nil {
              log.Fatal(err)
          }
          return m.Bounds()
      }
      

      Эта функция задаст состояние и зарегистрирует необходимость использования версии png функции image.Decode(). Эта регистрация происходит в качестве побочного эффекта импорта image/png.

      Возможно вы заметили пустой идентификатор (_) перед "image/png". Он необходим, потому что Go не позволяет импортировать пакеты, которые не используются в программе. При указании пустого идентификатора значение импорта отбрасывается так, что действует только побочный эффект импорта. Это означает, что хотя мы не вызываем пакет image/png в нашем коде, мы можем импортировать его ради побочного эффекта.

      Важно понимать, когда нужно импортировать пакет ради его побочного эффекта. Без надлежащей регистрации существует вероятность, что программа скомпилируется, но не будет правильно работать при запуске. В документации к пакетам стандартной библиотеки декларируется потребность в этом типе импорта. Если вы напишете пакет, который требуется импортировать ради побочного эффекта, вам также следует убедиться, что используемое выражение init() задокументировано, и импортирующие ваш пакет пользователи смогут правильно его использовать.

      Заключение

      В этом обучающем руководстве мы узнали, что функция init() загружается до остальной части кода приложения и может выполнять определенные задачи для пакета, в частности, инициализировать желаемое состояние. Также мы узнали, что порядок выполнения компилятором нескольких выражений init() зависит от того, в каком порядке компилятор загружает исходные файлы. Если вы хотите узнать больше о функции init(), ознакомьтесь с официальной документацией Golang или прочитайте дискуссию в сообществе Go об этой функции.

      Дополнительную информацию о функциях можно найти в статье Определение и вызов функций и в других статьях из серии статей по программированию на Go.



      Source link

      How To Set Up the Eclipse Theia Cloud IDE Platform on CentOS 7


      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      With developer tools moving to the cloud, adoption of cloud IDE (Integrated Development Environment) platforms is growing. Cloud IDEs are accessible from every type of modern device through web browsers, and they offer numerous advantages for real-time collaboration scenarios. Working in a cloud IDE provides a unified development and testing environment for you and your team, while minimizing platform incompatibilities. Accessible through web browsers, cloud IDEs are available from every type of modern device.

      Eclipse Theia is an extensible cloud IDE running on a remote server and accessible from a web browser. Visually, it’s designed to look and behave similarly to Microsoft Visual Studio Code, which means that it supports many programming languages, has a flexible layout, and has an integrated terminal. What separates Eclipse Theia from other cloud IDE software is its extensibility; it can be modified using custom extensions, which allow you to craft a cloud IDE suited to your needs.

      In this tutorial, you’ll deploy Eclipse Theia to your CentOS 7 server using Docker Compose, a container orchestration tool. You’ll expose it at your domain using nginx-proxy, an automated system for Docker that simplifies the process of configuring Nginx to serve as a reverse proxy for a container. You’ll also secure it using a free Let’s Encrypt TLS certificate, which you’ll provision using its specialized add-on. In the end, you’ll have Eclipse Theia running on your CentOS 7 server available via HTTPS and requiring the user to log in.

      Prerequisites

      Step 1 — Deploying nginx-proxy with Let’s Encrypt

      In this section, you’ll deploy nginx-proxy and its Let’s Encrypt add-on using Docker Compose. This will enable automatic TLS certificate provisioning and renewal, so that when you deploy Eclipse Theia it will be accessible at your domain via HTTPS.

      For the purposes of this tutorial, you’ll store all files under ~/eclipse-theia. Create the directory by running the following command:

      Navigate to it:

      You’ll store the Docker Compose configuration for nginx-proxy in a file named nginx-proxy-compose.yaml. Create it using your text editor:

      • vi nginx-proxy-compose.yaml

      Add the following lines:

      ~/eclipse-theia/nginx-proxy-compose.yaml

      version: '2'
      
      services:
        nginx-proxy:
          restart: always
          image: jwilder/nginx-proxy
          ports:
            - "80:80"
            - "443:443"
          volumes:
            - "/etc/nginx/htpasswd:/etc/nginx/htpasswd"
            - "/etc/nginx/vhost.d"
            - "/usr/share/nginx/html"
            - "/var/run/docker.sock:/tmp/docker.sock:ro"
            - "/etc/nginx/certs"
      
        letsencrypt-nginx-proxy-companion:
          restart: always
          image: jrcs/letsencrypt-nginx-proxy-companion
          volumes:
            - "/var/run/docker.sock:/var/run/docker.sock:ro"
          volumes_from:
            - "nginx-proxy"
      

      Here you’re defining two services that Docker Compose will run, nginx-proxy and its Let’s Encrypt companion. For the proxy, you specify jwilder/nginx-proxy as the image, map HTTP and HTTPS ports, and define volumes that will be accessible to it during runtime.

      Volumes are directories on your server that the defined service will have full access to, which you’ll later use to set up user authentication. To achieve that, you’ll make use of the first volume from the list, which maps the local /etc/nginx/htpasswd directory to the same one in the container. In that folder, nginx-proxy expects to find a file named exactly as the target domain, containing log-in credentials for user authentication in the htpasswd format (username:hashed_password).

      For the add-on, you name the Docker image and allow access to Docker’s socket by defining a volume. Then, you specify that the add-on should inherit access to the volumes defined for nginx-proxy. Both services have restart set to always, which orders Docker to restart the containers in case of crash or system reboot.

      Save and close the file.

      Deploy the configuration by running:

      • docker-compose -f nginx-proxy-compose.yaml up -d

      Here you pass in the nginx-proxy-compose.yaml filename to the -f parameter of the docker-compose command, which specifies the file to run. Then, you pass the up verb that instructs it to run the containers. The -d flag enables detached mode, which means that Docker Compose will run the containers in the background.

      The final output will look like this:

      Output

      Creating network "eclipse-theia_default" with the default driver Pulling nginx-proxy (jwilder/nginx-proxy:)... latest: Pulling from jwilder/nginx-proxy 8d691f585fa8: Pull complete 5b07f4e08ad0: Pull complete ... Digest: sha256:dfc0666b9747a6fc851f5fb9b03e65e957b34c95d9635b4b5d1d6b01104bde28 Status: Downloaded newer image for jwilder/nginx-proxy:latest Pulling letsencrypt-nginx-proxy-companion (jrcs/letsencrypt-nginx-proxy-companion:)... latest: Pulling from jrcs/letsencrypt-nginx-proxy-companion 89d9c30c1d48: Pull complete 668840c175f8: Pull complete ... Digest: sha256:a8d369d84079a923fdec8ce2f85827917a15022b0dae9be73e6a0db03be95b5a Status: Downloaded newer image for jrcs/letsencrypt-nginx-proxy-companion:latest Creating eclipse-theia_nginx-proxy_1 ... done Creating eclipse-theia_letsencrypt-nginx-proxy-companion_1 ... done

      You’ve deployed nginx-proxy and its Let’s Encrypt companion using Docker Compose. Now you’ll move on to set up Eclipse Theia at your domain and secure it.

      Step 2 — Deploying Dockerized Eclipse Theia

      In this section, you’ll create a file containing any allowed log-in combinations that a user will need to input. Then, you’ll deploy Eclipse Theia to your server using Docker Compose and expose it at your secured domain using nginx-proxy.

      As explained in the previous step, nginx-proxy expects log-in combinations to be in a file named after the exposed domain, in the htpasswd format and stored under the /etc/nginx/htpasswd directory in the container. The local directory which maps to the virtual one does not need to be the same, as was specified in the nginx-proxy config.

      To create log-in combinations, you’ll first need to install htpasswd by running the following command:

      • sudo yum install httpd-tools

      The httpd-tools package contains the htpasswd utility.

      Create the /etc/nginx/htpasswd directory:

      • sudo mkdir -p /etc/nginx/htpasswd

      Create a file that will store the logins for your domain:

      • sudo touch /etc/nginx/htpasswd/theia.your-domain

      Remember to replace theia.your-domain with your Eclipse Theia domain.

      To add a username and password combination, run the following command:

      • sudo htpasswd /etc/nginx/htpasswd/theia.your-domain username

      Replace username with the username you want to add. You’ll be asked for a password twice. After providing it, htpasswd will add the username and hashed password pair to the end of the file. You can repeat this command for as many logins as you wish to add.

      Now, you’ll create configuration for deploying Eclipse Theia. You’ll store it in a file named eclipse-theia-compose.yaml. Create it using your text editor:

      • vi eclipse-theia-compose.yaml

      Add the following lines:

      ~/eclipse-theia/eclipse-theia-compose.yaml

      version: '2.2'
      
      services:
        eclipse-theia:
          restart: always
          image: theiaide/theia:next
          init: true
          environment:
            - VIRTUAL_HOST=theia.your-domain
            - LETSENCRYPT_HOST=theia.your-domain
      

      In this config, you define a single service called eclipse-theia with restart set to always and theiaide/theia:next as the container image. You also set init to true to instruct Docker to use init as the main process manager when running Eclipse Theia inside the container.

      Then, you specify two environment variables in the environment section: VIRTUAL_HOST and LETSENCRYPT_HOST. The former is passed on to nginx-proxy and tells it at what domain the container should be exposed, while the latter is used by its Let’s Encrypt add-on and specifies for which domain to request TLS certificates. Unless you specify a wildcard as the value for VIRTUAL_HOST, they must be the same.

      Remember to replace theia.your-domain with your desired domain, then save and close the file.

      Now deploy Eclipse Theia by running:

      • docker-compose -f eclipse-theia-compose.yaml up -d

      The final output will look like:

      Output

      ... Pulling eclipse-theia (theiaide/theia:next)... next: Pulling from theiaide/theia 63bc94deeb28: Pull complete 100db3e2539d: Pull complete ... Digest: sha256:c36dff04e250f1ac52d13f6d6e15ab3e9b8cad9ad68aba0208312e0788ecb109 Status: Downloaded newer image for theiaide/theia:next Creating eclipse-theia_eclipse-theia_1 ... done

      Then, in your browser, navigate to the domain you’re using for Eclipse Theia. Your browser will show you a prompt asking you to log in. After providing the correct credentials, you’ll enter Eclipse Theia and immediately see its editor GUI. In your address bar you’ll see a padlock indicating that the connection is secure. If you don’t see this immediately, wait a few minutes for the TLS certificates to provision, then reload the page.

      Eclipse Theia GUI

      Now that you can securely access your cloud IDE, you’ll start using the editor in the next step.

      Step 3 — Using the Eclipse Theia Interface

      In this section, you’ll explore some of the features of the Eclipse Theia interface.

      On the left-hand side of the IDE, there is a vertical row of four buttons opening the most commonly used features in a side panel.

      Eclipse Theia GUI - Sidepanel

      This bar is customizable so you can move these views to a different order or remove them from the bar. By default, the first view opens the Explorer panel that provides tree-like navigation of the project’s structure. You can manage your folders and files here—creating, deleting, moving, and renaming them as necessary.

      After creating a new file through the File menu, you’ll see an empty file open in a new tab. Once saved, you can view the file’s name in the Explorer side panel. To create folders right click on the Explorer sidebar and click on New Folder. You can expand a folder by clicking on its name as well as dragging and dropping files and folders to upper parts of the hierarchy to move them to a new location.

      Eclipse Theia GUI - New Folder

      The next two options provide access to search and replace functionality. Following it, the next one provides a view of source control systems that you may be using, such as Git.

      The final view is the debugger option, which provides all the common actions for debugging in the panel. You can save debugging configurations in the launch.json file.

      Debugger View with launch.json open

      The central part of the GUI is your editor, which you can separate by tabs for your code editing. You can change your editing view to a grid system or to side-by-side files. Like all modern IDEs, Eclipse Theia supports syntax highlighting for your code.

      Editor Grid View

      You can gain access to a terminal by typing CTRL+SHIFT+`, or by clicking on Terminal in the upper menu, and selecting New Terminal. The terminal will open in a lower panel and its working directory will be set to the project’s workspace, which contains the files and folders shown in the Explorer side panel.

      Terminal open

      You’ve explored a high-level overview of the Eclipse Theia interface and reviewed some of the most commonly used features.

      Conclusion

      You now have Eclipse Theia, a versatile cloud IDE, installed on your CentOS 7 server using Docker Compose and nginx-proxy. You’ve secured it with a free Let’s Encrypt TLS certificate and set up the instance to require log-in credentials from the user. You can work on your source code and documents with it individually or collaborate with your team. You can also try building your own version of Eclipse Theia if you need additional functionality. For further information on how to do that, visit the Theia docs.



      Source link

      Знакомство с картами в Go


      В большинстве современных языков программирования применяется концепция словаря или хэша. Эти типы обычно используются для хранения данных в парах, где ключ соответствует значению.

      В Go тип данных map используется как тип dictionary в большинстве других языков программирования. Он сопоставляет ключи со значениями, создавая пары ключ-значение, представляющие собой полезный способ хранения данных в Go. Построение карты осуществляется с помощью ключевого слова map с последующим указанием типа данных ключа в квадратных скобках [ ] и типа данных значения. Пары ключ-значение заключаются в фигурные скобки { }:

      map[key]value{}
      

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

      map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      

      В дополнение к фигурным скобкам в карте используются двоеточия, соединяющие пары ключ-значение. Слова слева от двоеточий являются ключами. Ключи могут иметь любой сравниваемый тип в Go, в том числе строки, целые числа и т. д.

      Ключи в примере карты:

      • "name"
      • "animal"
      • "color"
      • "location"

      Слова справа от двоеточий являются значениями. Значения могут относиться к любому типу данных. Значения в примере карты:

      • "Sammy"
      • "shark"
      • "blue"
      • "ocean"

      Как и другие типы данных, карты могут храниться в переменных и выводиться:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(sammy)
      

      Результат будет выглядеть так:

      Output
      map[animal:shark color:blue location:ocean name:Sammy]

      Порядок пар ключ-значение можно изменять. Тип данных карт Go является неупорядоченным. Вне зависимости от порядка, пары ключ-значение останутся без изменений, что даст возможность доступа к данным по их относительному значению.

      Доступ к элементам карты

      Вы можете вызывать значения карты, ссылаясь на соответствующие ключи. Поскольку карты предоставляют пары ключ-значение для хранения данных, они могут стать полезным элементом вашей программы Go.

      Если вы хотите изолировать имя пользователя Sammy, вы можете вызвать для этого переменную sammy["name"], которая содержит карту и связанный ключ. Распечатаем результат:

      fmt.Println(sammy["name"])
      

      Получим значение в качестве результата:

      Output
      Sammy

      Карты похожи на базы данных, однако вместо вызова целого числа для получения определенного значения индекса, как в срезе, вы присваиваете значение ключу и вызываете ключ для получения связанного значения.

      Вызывая ключ "name", вы получаете значение этого ключа, то есть "Sammy".

      Также вы можете вызывать остальные значения карты sammy в том же формате:

      fmt.Println(sammy["animal"])
      // returns shark
      
      fmt.Println(sammy["color"])
      // returns blue
      
      fmt.Println(sammy["location"])
      // returns ocean
      

      Используя пары ключ-значение в типах данных карт, вы можете ссылаться на ключи для получения значений.

      Ключи и значения

      В отличие от некоторых языков программирования, в Go отсутствуют удобные функции вывода списка ключей или значений карты. В качестве примера такой функции можно назвать метод Python .keys() для словарей. Однако он поддерживает итерацию с использованием оператора range:

      for key, value := range sammy {
          fmt.Printf("%q is the key for the value %qn", key, value)
      }
      

      При ранжировании карты в Go выводится два значения. Первое значение будет ключом, а второе — значением. Go создаст эти переменные с правильным типом данных. В данном случае ключ карты представлял собой строку, поэтому ключ также будет строкой. Значение также является строкой:

      Output
      "animal" is the key for the value "shark" "color" is the key for the value "blue" "location" is the key for the value "ocean" "name" is the key for the value "Sammy"

      Чтобы получить только список ключей, вы можете еще раз использовать оператор range. Чтобы получить доступ только к ключам, вы можете декларировать только одну переменную:

      keys := []string{}
      
      for key := range sammy {
          keys = append(keys, key)
      }
      fmt.Printf("%q", keys)
      

      Вначале программа декларирует срез для хранения ваших ключей.

      В результатах будут показаны только ключи вашей карты:

      Output
      ["color" "location" "name" "animal"]

      Ключи не сортируются. Если вы хотите сортировать их, используйте функцию sort.Strings из пакета sort:

      sort.Strings(keys)
      

      С помощью этой функции вы получите следующие результаты:

      Output
      ["animal" "color" "location" "name"]

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

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      
      items := make([]string, len(sammy))
      
      var i int
      
      for _, v := range sammy {
          items[i] = v
          i++
      }
      fmt.Printf("%q", items)
      

      Вначале вы декларируете срез для хранения ключей. Поскольку вы знаете, сколько вам требуется элементов, вы можете избежать несоответствия при выделении памяти, определив точный размер среза. После этого вы декларируете переменную индекса. Поскольку вам не нужен ключ, вы используете оператор _ при запуске цикла, чтобы игнорировать значение ключа. Результат будет выглядеть так:

      Output
      ["ocean" "Sammy" "shark" "blue"]

      Чтобы определить количество элементов на карте, вы можете использовать встроенную функцию len:

      sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
      fmt.Println(len(sammy))
      

      В результатах будет показано количество элементов на вашей карте:

      Output
      4

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

      Проверка существования

      Если запрошенный ключ отсутствует, карты Go возвращают нулевое значение для типа значений карты. Поэтому вам нужен альтернативный способ отличить сохраненный ноль от отсутствующего ключа.

      Поищем в карте заведомо несуществующее значение и посмотрим на результат:

      counts := map[string]int{}
      fmt.Println(counts["sammy"])
      

      Вывод должен выглядеть так:

      Output
      0

      Хотя ключ sammy отсутствует в карте, Go возвращает значение 0. Это связано с тем, что используется тип данных значения int, и поскольку в Go задано нулевое значение всех переменных, возвращается нулевое значение 0.

      Во многих случаях такое поведение нежелательно и может вызвать ошибку в программе. При поиске значения на карте Go может вывести второе опциональное значение. Это второе значение является булевым значением true в случае обнаружения ключа или false в случае отсутствия ключа. В Go это называется идиомой ok. Хотя вы можете присвоить любое имя переменной, получающей второй аргумент, в Go всегда следует использовать имя ok:

      count, ok := counts["sammy"]
      

      Если ключ sammy существует в карте counts, ok будет иметь значение true. В противном случае ok будет иметь значение false.

      Вы можете использовать переменную ok для определения действий в программе:

      if ok {
          fmt.Printf("Sammy has a count of %dn", count)
      } else {
          fmt.Println("Sammy was not found")
      }
      

      Результат будет выглядеть следующим образом:

      Output
      Sammy was not found

      В Go вы можете комбинировать декларирование переменных и условную проверку с помощью блока if/else. Это позволяет использовать для такой проверки одно выражение:

      if count, ok := counts["sammy"]; ok {
          fmt.Printf("Sammy has a count of %dn", count)
      } else {
          fmt.Println("Sammy was not found")
      }
      

      При получении значения из карты в Go всегда полезно проверить его существование, чтобы избежать ошибок в программе.

      Изменение карт

      Карты — это мутируемая структура данных, и вы можете изменять их. В этом разделе мы рассмотрим добавление и удаление элементов карт.

      Добавление и изменение элементов карт

      Вы можете добавлять на карты пары ключ-значение, не используя методы или функции. Для этого используется имя переменной карт, за которым идет значение ключа в квадратных скобках [ ] и оператор равенства = для определения нового значения:

      map[key] = value
      

      Покажем это на примере добавления пары ключ-значение в карту с именем usernames:

      usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
      
      usernames["Drew"] = "squidly"
      fmt.Println(usernames)
      

      В результате на карте будет выведена новая пара ключ-значение Drew:squidly:

      Output
      map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]

      Поскольку карты выводятся в неупорядоченном виде, эта пара может появиться в карте где угодно. Если вы используете карту usernames в следующих частях вашего файла программы, она будет содержать дополнительную пару ключ-значение.

      Также этот синтаксис можно использовать для изменения значения, назначенного ключу. В этом случае вы ссылаетесь на существующий ключ и передаете ему новое значение.

      Рассмотрим карту с именем followers, которая отслеживает подписчиков пользователей определенной сети. У пользователя "drew" сегодня выросло число подписчиков, и поэтому нужно обновить целочисленное значение, которое передается для ключа "drew". Мы используем функцию Println() для проверки изменения карты:

      followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
      followers["drew"] = 342
      fmt.Println(followers)
      

      В результатах будет показано обновленное значение drew:

      Output
      map[cindy:918 drew:342 mary:428]

      Мы видим, что количество подписчиков увеличилось с целого числа 305 до 342.

      Вы можете использовать этот метод для добавления пар ключ-значение в карты с вводимыми пользователем данными. Напишем небольшую программу usernames.go, которая выполняется в командной строке и позволяет пользователю вводить дополнительные имена и связанные с ними имена пользователей:

      usernames.go
      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}
      
          for {
              fmt.Println("Enter a name:")
      
              var name string
              _, err := fmt.Scanln(&name)
      
              if err != nil {
                  panic(err)
              }
      
              name = strings.TrimSpace(name)
      
              if u, ok := usernames[name]; ok {
                  fmt.Printf("%q is the username of %qn", u, name)
                  continue
              }
      
              fmt.Printf("I don't have %v's username, what is it?n", name)
      
              var username string
              _, err = fmt.Scanln(&username)
      
              if err != nil {
                  panic(err)
              }
      
              username = strings.TrimSpace(username)
      
              usernames[name] = username
      
              fmt.Println("Data updated.")
          }
      }
      

      Вначале мы определим в файле usernames.go первоначальную карту. Затем мы зададим цикл итерации имен. Мы предлагаем пользователю ввести имя и декларируем переменную для его сохранения. Затем мы проверяем наличие ошибок, и если они есть, в программе возникает паника, и она закрывается. Поскольку Scanln получает все вводимые данные, включая символ возврата каретки, нам нужно удалить из вводимых данных все пробелы. Для этого мы используем функцию strings.TrimSpace.

      Блок if проверяет наличие имени на карте и выводит обратную связь. Если имя присутствует на карте, программа возвращается в начало цикла. Если имени нет на карте, пользователю направляется обратная связь и предлагается ввести новое имя пользователя для данного имени. Затем программа снова проверяет наличие ошибок. При отсутствии ошибок программа удаляет символ возврата каретки, назначает значение имени пользователя ключу name и выводит сообщение об обновлении данных.

      Запустим эту программу в командной строке:

      • go run usernames.go

      Вывод должен выглядеть так:

      Output
      Enter a name: Sammy "sammy-shark" is the username of "Sammy" Enter a name: Jesse I don't have Jesse's username, what is it? JOctopus Data updated. Enter a name:

      После завершения тестирования нажмите CTRL + C для выхода из программы.

      Это показывает возможность интерактивного изменения карт. В данной конкретной программе при выходе с помощью клавиш CTRL + C вы потеряете все данные, если не реализуете способ чтения и записи файлов.

      Таким образом, мы можем добавлять элементы на карты или изменять их значения с помощью синтаксиса map[key] = value.

      Удаление элементов карт

      Мы можем не только добавлять пары ключ-значение и изменять значения типа данных карты, но и удалять элементы карт.

      Чтобы удалить пару ключ-значение с карты, можно использовать встроенную функцию delete(). Первый аргумент — это карта, откуда мы удаляем элемент. Второй аргумент — это удаляемый ключ:

      delete(map, key)
      

      Определим карту разрешений:

      permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}
      

      Нам больше не нужно разрешение modify, поэтому мы удалим его с нашей карты. Затем мы выведем карту, чтобы убедиться в его удалении:

      permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
      delete(permissions, 16)
      fmt.Println(permissions)
      

      Результат подтвердит удаление:

      Output
      map[1:read 2:write 4:delete 8:create]

      Строка delete(permissions, 16) удаляет пару ключ-значение 16:"modify" из карты permissions.

      Если вы хотите очистить карту от всех значений, вы можете задать ее равной пустой карте того же типа. При этом будет создана новая пустая карта, а сборщик мусора очистит память от старой карты.

      Удалим все элементы из карты permissions:

      permissions = map[int]string{}
      fmt.Println(permissions)
      

      Результаты показывают, что мы получили пустую карту без пар ключ-значение:

      Output
      map[]

      Поскольку карты имеют мутируемый тип данных, они поддерживают добавление, изменение, удаление и очистку элементов.

      Заключение

      В этом материале мы рассказали о структуре карт в Go. Карты состоят из пар ключ-значение и предоставляют возможность хранения данных без использования индексов. Это позволяет получать значения на основе их смысла и связи с другими типами данных.



      Source link