{"id":1616,"date":"2026-09-14T10:18:50","date_gmt":"2026-09-14T13:18:50","guid":{"rendered":"https:\/\/www.linuxpro.com.br\/?p=1616"},"modified":"2026-09-14T10:20:55","modified_gmt":"2026-09-14T13:20:55","slug":"api-do-virtualbox-em-go-liste-vms-com-soap","status":"publish","type":"post","link":"https:\/\/www.linuxpro.com.br\/es\/2026\/09\/api-do-virtualbox-em-go-liste-vms-com-soap\/","title":{"rendered":"API de VirtualBox \u2014 parte 2: cliente SOAP en Go"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/09\/virtualbox-api-golang-v1.webp\" alt=\"Mascote LinuxPro e cachorro caramelo cyborg em laborat\u00f3rio com m\u00e1quinas virtuais, Gopher do Go e logo VirtualBox.\" width=\"1486\" height=\"856\" \/><\/p>\n<p>Vamos implementar um cliente em Go que autentica no web service do VirtualBox, lista UUID, nome e estado das m\u00e1quinas e encerra a sess\u00e3o. O programa utiliza somente a biblioteca padr\u00e3o e produz um invent\u00e1rio JSON, sem executar a\u00e7\u00f5es de escrita sobre as VMs.<\/p>\n<p>Esta \u00e9 a <strong>parte 2<\/strong>. Se voc\u00ea ainda n\u00e3o conhece o servi\u00e7o, leia primeiro a <a href=\"\/2026\/09\/api-do-virtualbox-parte-1-soap-sessoes-e-seguranca\/\">parte 1: SOAP, sess\u00f5es e seguran\u00e7a na API do VirtualBox<\/a>. L\u00e1 est\u00e3o a prepara\u00e7\u00e3o do <code>vboxwebsrv<\/code>, o t\u00fanel SSH e a diferen\u00e7a entre refer\u00eancias e UUIDs.<\/p>\n<h2>Pr\u00e9-requisitos do exemplo<\/h2>\n<p>Tenha Go instalado e um host VirtualBox com <code>vboxwebsrv<\/code> ativo, autentica\u00e7\u00e3o configurada e acesso protegido. Os comandos abaixo usam <code>http:\/\/127.0.0.1:18083\/<\/code>: execute o cliente no mesmo host ou use o t\u00fanel explicado na parte 1. N\u00e3o desabilite a autentica\u00e7\u00e3o nem exponha essa porta diretamente \u00e0 internet.<\/p>\n<h2>Qual contrato foi usado e o que foi testado<\/h2>\n<p>As opera\u00e7\u00f5es e seus par\u00e2metros foram conferidos no WSDL inclu\u00eddo no <a href=\"https:\/\/download.virtualbox.org\/virtualbox\/7.2.0\/\">SDK oficial 7.2.0<\/a>, usado como refer\u00eancia documental da linha 7.2. Isso n\u00e3o \u00e9 recomenda\u00e7\u00e3o para instalar um patch antigo: use uma vers\u00e3o mantida do VirtualBox e confira o SDK correspondente ao seu ambiente.<\/p>\n<p><strong>Valida\u00e7\u00e3o deste artigo:<\/strong> o c\u00f3digo foi compilado, verificado com <code>go vet<\/code> e testado com servidor SOAP simulado e detector de corridas. Tamb\u00e9m verificamos estruturas de requisi\u00e7\u00e3o contra o XSD do SDK. N\u00e3o houve teste de uma sess\u00e3o autenticada em um host VirtualBox real. Portanto, a integra\u00e7\u00e3o precisa passar por homologa\u00e7\u00e3o no seu laborat\u00f3rio antes de uso operacional.<\/p>\n<p>Os testes adicionais cobriram erro SOAP, XML inv\u00e1lido, resposta excessiva, redirecionamento, cancelamento, escape de caracteres e limpeza de sess\u00e3o ap\u00f3s falha. O exemplo n\u00e3o pretende substituir um SDK completo.<\/p>\n<h2>1. Criar o projeto Go<\/h2>\n<pre><code class=\"language-bash\">mkdir vbox-inventory\ncd vbox-inventory\ngo mod init example.com\/vbox-inventory<\/code><\/pre>\n<p>O exemplo utiliza <code>errors.Join<\/code>, dispon\u00edvel a partir do Go 1.20; prefira uma vers\u00e3o atualmente mantida. N\u00e3o h\u00e1 depend\u00eancias externas para baixar. Vamos separar o transporte SOAP em <code>client.go<\/code> e o invent\u00e1rio em <code>main.go<\/code>.<\/p>\n<h2>2. Implementar o transporte SOAP<\/h2>\n<p>Salve como <code>client.go<\/code>. O cliente aceita HTTP apenas para loopback, n\u00e3o segue redirecionamentos, limita a resposta e deixa a valida\u00e7\u00e3o TLS padr\u00e3o ativa:<\/p>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t&quot;bytes&quot;\n\t&quot;context&quot;\n\t&quot;encoding\/xml&quot;\n\t&quot;errors&quot;\n\t&quot;fmt&quot;\n\t&quot;io&quot;\n\t&quot;net&quot;\n\t&quot;net\/http&quot;\n\t&quot;net\/url&quot;\n\t&quot;time&quot;\n)\n\nconst apiNS = &quot;http:\/\/www.virtualbox.org\/&quot;\nconst soapNS = &quot;http:\/\/schemas.xmlsoap.org\/soap\/envelope\/&quot;\nconst maxResponse = 2 &lt;&lt; 20\n\ntype client struct {\n\tendpoint string\n\thttp     *http.Client\n}\n\ntype field struct{ name, value string }\n\ntype reply struct {\n\tXMLName xml.Name `xml:&quot;http:\/\/schemas.xmlsoap.org\/soap\/envelope\/ Envelope&quot;`\n\tBody    struct {\n\t\tFault  *struct{} `xml:&quot;http:\/\/schemas.xmlsoap.org\/soap\/envelope\/ Fault&quot;`\n\t\tResult struct {\n\t\t\tXMLName xml.Name\n\t\t\tValues  []string `xml:&quot;returnval&quot;`\n\t\t} `xml:&quot;,any&quot;`\n\t} `xml:&quot;http:\/\/schemas.xmlsoap.org\/soap\/envelope\/ Body&quot;`\n}\n\nfunc newClient(endpoint string) (*client, error) {\n\tu, err := url.Parse(endpoint)\n\tif err != nil || u.Host == &quot;&quot; || u.User != nil || u.RawQuery != &quot;&quot; || u.Fragment != &quot;&quot; {\n\t\treturn nil, errors.New(&quot;endpoint inv\u00e1lido: use URL sem credenciais ou par\u00e2metros&quot;)\n\t}\n\tip := net.ParseIP(u.Hostname())\n\tloopback := u.Hostname() == &quot;localhost&quot; || (ip != nil &amp;&amp; ip.IsLoopback())\n\tif u.Scheme != &quot;https&quot; &amp;&amp; !(u.Scheme == &quot;http&quot; &amp;&amp; loopback) {\n\t\treturn nil, errors.New(&quot;use HTTPS; HTTP s\u00f3 \u00e9 permitido no loopback&quot;)\n\t}\n\ttransport := http.DefaultTransport.(*http.Transport).Clone()\n\ttransport.Proxy = nil \/\/ N\u00e3o encaminhar credenciais por proxy do ambiente.\n\treturn &amp;client{\n\t\tendpoint: endpoint,\n\t\thttp: &amp;http.Client{\n\t\t\tTransport:     transport,\n\t\t\tTimeout:       10 * time.Second,\n\t\t\tCheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },\n\t\t},\n\t}, nil\n}\n\nfunc (c *client) call(ctx context.Context, method string, fields ...field) (values []string, err error) {\n\tswitch method {\n\tcase &quot;IWebsessionManager_logon&quot;, &quot;IWebsessionManager_logoff&quot;, &quot;IVirtualBox_getMachines&quot;,\n\t\t&quot;IMachine_getId&quot;, &quot;IMachine_getName&quot;, &quot;IMachine_getState&quot;:\n\tdefault:\n\t\treturn nil, errors.New(&quot;opera\u00e7\u00e3o fora do escopo somente leitura&quot;)\n\t}\n\tvar params bytes.Buffer\n\tenc := xml.NewEncoder(&amp;params)\n\tfor _, f := range fields {\n\t\tif err := enc.EncodeElement(f.value, xml.StartElement{Name: xml.Name{Local: f.name}}); err != nil {\n\t\t\treturn nil, fmt.Errorf(&quot;codificar par\u00e2metros: %w&quot;, err)\n\t\t}\n\t}\n\tif err := enc.Flush(); err != nil {\n\t\treturn nil, fmt.Errorf(&quot;finalizar XML: %w&quot;, err)\n\t}\n\tbody := fmt.Sprintf(\n\t\t`&lt;s:Envelope xmlns:s=&quot;%s&quot; xmlns:v=&quot;%s&quot;&gt;&lt;s:Body&gt;&lt;v:%s&gt;%s&lt;\/v:%s&gt;&lt;\/s:Body&gt;&lt;\/s:Envelope&gt;`,\n\t\tsoapNS, apiNS, method, params.String(), method,\n\t)\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, bytes.NewBufferString(body))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(&quot;criar requisi\u00e7\u00e3o: %w&quot;, err)\n\t}\n\treq.Header.Set(&quot;Content-Type&quot;, &quot;text\/xml; charset=utf-8&quot;)\n\treq.Header.Set(&quot;SOAPAction&quot;, `&quot;&quot;`)\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(&quot;transportar %s: %w&quot;, method, err)\n\t}\n\tdefer func() { err = errors.Join(err, res.Body.Close()) }()\n\tdata, err := io.ReadAll(io.LimitReader(res.Body, maxResponse+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(&quot;ler resposta: %w&quot;, err)\n\t}\n\tif len(data) &gt; maxResponse {\n\t\treturn nil, errors.New(&quot;resposta excede 2 MiB&quot;)\n\t}\n\tvar decoded reply\n\tdecodeErr := xml.Unmarshal(data, &amp;decoded)\n\tif decodeErr == nil &amp;&amp; decoded.Body.Fault != nil {\n\t\t\/\/ N\u00e3o imprimir faultstring\/detail: podem revelar informa\u00e7\u00f5es do host.\n\t\treturn nil, fmt.Errorf(&quot;falha SOAP em %s (HTTP %d)&quot;, method, res.StatusCode)\n\t}\n\tif res.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(&quot;%s: HTTP %d&quot;, method, res.StatusCode)\n\t}\n\tif decodeErr != nil {\n\t\treturn nil, fmt.Errorf(&quot;decodificar resposta: %w&quot;, decodeErr)\n\t}\n\tresult := decoded.Body.Result\n\tif result.XMLName.Space != apiNS || result.XMLName.Local != method+&quot;Response&quot; {\n\t\treturn nil, errors.New(&quot;resposta SOAP inesperada&quot;)\n\t}\n\treturn result.Values, nil\n}\n\nfunc (c *client) one(ctx context.Context, method string, fields ...field) (string, error) {\n\tvalues, err := c.call(ctx, method, fields...)\n\tif err != nil {\n\t\treturn &quot;&quot;, err\n\t}\n\tif len(values) != 1 || values[0] == &quot;&quot; {\n\t\treturn &quot;&quot;, errors.New(&quot;retorno escalar vazio ou inv\u00e1lido&quot;)\n\t}\n\treturn values[0], nil\n}<\/code><\/pre>\n<p>O <code>xml.Encoder<\/code> cuida do escape dos valores. N\u00e3o concatenamos a senha diretamente como XML. O namespace, as opera\u00e7\u00f5es e o <code>SOAPAction<\/code> vazio seguem o contrato consultado; erros HTTP e SOAP s\u00e3o tratados separadamente.<\/p>\n<p>Detalhes completos de falhas do servidor foram omitidos da mensagem para reduzir exposi\u00e7\u00e3o de informa\u00e7\u00f5es. Em um produto real, voc\u00ea pode classificar os tipos de falha internamente, mantendo segredos e detalhes sens\u00edveis fora dos logs. Veja as refer\u00eancias de <a href=\"https:\/\/pkg.go.dev\/encoding\/xml\">encoding\/xml<\/a> e <a href=\"https:\/\/pkg.go.dev\/net\/http\">net\/http<\/a>.<\/p>\n<h2>3. Listar as m\u00e1quinas e garantir o logoff<\/h2>\n<p>Salve como <code>main.go<\/code>:<\/p>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t&quot;context&quot;\n\t&quot;encoding\/json&quot;\n\t&quot;errors&quot;\n\t&quot;fmt&quot;\n\t&quot;os&quot;\n\t&quot;time&quot;\n)\n\ntype machine struct {\n\tID    string `json:&quot;id&quot;`\n\tName  string `json:&quot;name&quot;`\n\tState string `json:&quot;state&quot;`\n}\n\nfunc inventory(ctx context.Context, c *client, username, password string) (vms []machine, err error) {\n\tref, err := c.one(ctx, &quot;IWebsessionManager_logon&quot;,\n\t\tfield{name: &quot;username&quot;, value: username},\n\t\tfield{name: &quot;password&quot;, value: password},\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t\/\/ A limpeza precisa funcionar mesmo se o contexto principal expirar.\n\t\tcleanup, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\t_, closeErr := c.call(cleanup, &quot;IWebsessionManager_logoff&quot;, field{name: &quot;refIVirtualBox&quot;, value: ref})\n\t\tif closeErr != nil {\n\t\t\terr = errors.Join(err, fmt.Errorf(&quot;encerrar sess\u00e3o: %w&quot;, closeErr))\n\t\t}\n\t}()\n\trefs, err := c.call(ctx, &quot;IVirtualBox_getMachines&quot;, field{name: &quot;_this&quot;, value: ref})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvms = make([]machine, 0, len(refs))\n\tfor _, machineRef := range refs {\n\t\ttarget := field{name: &quot;_this&quot;, value: machineRef}\n\t\tid, err := c.one(ctx, &quot;IMachine_getId&quot;, target)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tname, err := c.one(ctx, &quot;IMachine_getName&quot;, target)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstate, err := c.one(ctx, &quot;IMachine_getState&quot;, target)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvms = append(vms, machine{ID: id, Name: name, State: state})\n\t}\n\treturn vms, nil\n}\n\nfunc run() error {\n\tendpoint := os.Getenv(&quot;VBOX_URL&quot;)\n\tif endpoint == &quot;&quot; {\n\t\tendpoint = &quot;http:\/\/127.0.0.1:18083\/&quot;\n\t}\n\tusername, password := os.Getenv(&quot;VBOX_USER&quot;), os.Getenv(&quot;VBOX_PASSWORD&quot;)\n\tif username == &quot;&quot; || password == &quot;&quot; {\n\t\treturn errors.New(&quot;defina VBOX_USER e VBOX_PASSWORD&quot;)\n\t}\n\tc, err := newClient(endpoint)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer c.http.CloseIdleConnections()\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\tvms, err := inventory(ctx, c, username, password)\n\tif err != nil {\n\t\treturn err\n\t}\n\tout := json.NewEncoder(os.Stdout)\n\tout.SetIndent(&quot;&quot;, &quot;  &quot;)\n\tif err := out.Encode(vms); err != nil {\n\t\treturn fmt.Errorf(&quot;escrever invent\u00e1rio: %w&quot;, err)\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, &quot;erro:&quot;, err)\n\t\tos.Exit(1)\n\t}\n}<\/code><\/pre>\n<p>O <code>defer<\/code> para limpeza \u00e9 registrado assim que o login retorna uma refer\u00eancia v\u00e1lida. Ele usa um contexto independente e curto: se a consulta principal expirar, ainda haver\u00e1 uma tentativa de encerrar a sess\u00e3o. Uma falha no logoff n\u00e3o \u00e9 silenciosamente descartada.<\/p>\n<p>A execu\u00e7\u00e3o \u00e9 sequencial por clareza. O nome, estado e UUID exigem consultas separadas; n\u00e3o \u00e9 um coletor otimizado para invent\u00e1rios enormes. O programa interrompe a coleta no primeiro erro e n\u00e3o apresenta um invent\u00e1rio parcial como se fosse completo. A restri\u00e7\u00e3o de opera\u00e7\u00f5es \u00e9 do cliente did\u00e1tico, n\u00e3o uma pol\u00edtica de autoriza\u00e7\u00e3o aplicada pelo servidor.<\/p>\n<h2>4. Compilar e executar sem colocar a senha no hist\u00f3rico<\/h2>\n<p>No Bash, compile e leia a senha sem escrev\u00ea-la na linha de comando:<\/p>\n<pre><code class=\"language-bash\">gofmt -w client.go main.go\ngo vet .\/...\ngo build -o vbox-inventory .\n\nexport VBOX_URL=&#x27;http:\/\/127.0.0.1:18083\/&#x27;\nIFS= read -r -p &#x27;Usu\u00e1rio do servi\u00e7o: &#x27; VBOX_USER\nexport VBOX_USER\nIFS= read -r -s -p &#x27;Senha do servi\u00e7o: &#x27; VBOX_PASSWORD\nprintf &#x27;\\n&#x27;\nexport VBOX_PASSWORD\n.\/vbox-inventory\nunset VBOX_PASSWORD<\/code><\/pre>\n<p>Vari\u00e1veis de ambiente n\u00e3o s\u00e3o um cofre: processos privilegiados e ferramentas de diagn\u00f3stico podem exp\u00f4-las. Em automa\u00e7\u00e3o, integre um mecanismo de segredos apropriado e n\u00e3o imprima ambiente, XML de login ou refer\u00eancias de sess\u00e3o.<\/p>\n<p>Formato ilustrativo de sa\u00edda, n\u00e3o capturado de um host real:<\/p>\n<pre><code class=\"language-json\">[\n  {\n    &quot;id&quot;: &quot;11111111-2222-3333-4444-555555555555&quot;,\n    &quot;name&quot;: &quot;Linux lab&quot;,\n    &quot;state&quot;: &quot;PoweredOff&quot;\n  }\n]<\/code><\/pre>\n<p>Se a conta n\u00e3o tiver m\u00e1quinas registradas, a sa\u00edda ser\u00e1 <code>[]<\/code>. Um erro faz o programa terminar com c\u00f3digo diferente de zero. Antes de concluir que o invent\u00e1rio est\u00e1 errado, confirme qual ambiente de usu\u00e1rio o servi\u00e7o est\u00e1 expondo.<\/p>\n<h2>5. Um teste automatizado sem hipervisor<\/h2>\n<p>Salve este teste como <code>client_test.go<\/code>. Ele verifica que um SOAP Fault produz erro sem reproduzir os detalhes privados do servidor:<\/p>\n<pre><code class=\"language-go\">package main\n\nimport (\n\t&quot;context&quot;\n\t&quot;io&quot;\n\t&quot;net\/http&quot;\n\t&quot;net\/http\/httptest&quot;\n\t&quot;strings&quot;\n\t&quot;testing&quot;\n)\n\nfunc TestSOAPFaultIsNotSuccess(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\tbody := `&lt;s:Envelope xmlns:s=&quot;http:\/\/schemas.xmlsoap.org\/soap\/envelope\/&quot;&gt;` +\n\t\t\t`&lt;s:Body&gt;&lt;s:Fault&gt;&lt;faultstring&gt;private-host-detail&lt;\/faultstring&gt;&lt;\/s:Fault&gt;&lt;\/s:Body&gt;&lt;\/s:Envelope&gt;`\n\t\tif _, err := io.WriteString(w, body); err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}))\n\tdefer server.Close()\n\tc, err := newClient(server.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer c.http.CloseIdleConnections()\n\t_, err = c.call(context.Background(), &quot;IVirtualBox_getMachines&quot;, field{name: &quot;_this&quot;, value: &quot;test-ref&quot;})\n\tif err == nil {\n\t\tt.Fatal(&quot;SOAP Fault deveria produzir erro&quot;)\n\t}\n\tif strings.Contains(err.Error(), &quot;private-host-detail&quot;) {\n\t\tt.Fatal(&quot;erro exp\u00f4s detalhes do host&quot;)\n\t}\n}<\/code><\/pre>\n<pre><code class=\"language-bash\">go test -race -count=1 .\/...<\/code><\/pre>\n<p>Esse teste exercita o transporte com um servidor simulado. Ele n\u00e3o comprova credenciais, permiss\u00f5es ou compatibilidade operacional com seu VirtualBox. Complete a homologa\u00e7\u00e3o com uma m\u00e1quina de laborat\u00f3rio e compare UUID, nome e estado com a administra\u00e7\u00e3o local.<\/p>\n<h2>Falhas comuns e diagn\u00f3stico<\/h2>\n<table>\n<thead>\n<tr>\n<th>Problema<\/th>\n<th>O que verificar<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Conex\u00e3o recusada<\/td>\n<td>Servi\u00e7o ativo, endere\u00e7o, porta e t\u00fanel SSH.<\/td>\n<\/tr>\n<tr>\n<td>Falha SOAP no login<\/td>\n<td>Autentica\u00e7\u00e3o configurada, usu\u00e1rio e senha; n\u00e3o desligue a prote\u00e7\u00e3o como corre\u00e7\u00e3o.<\/td>\n<\/tr>\n<tr>\n<td>Lista vazia<\/td>\n<td>Usu\u00e1rio que executa o servi\u00e7o e m\u00e1quinas registradas naquele ambiente.<\/td>\n<\/tr>\n<tr>\n<td>Refer\u00eancia inv\u00e1lida<\/td>\n<td>Sess\u00e3o expirada ou refer\u00eancia reutilizada de outra execu\u00e7\u00e3o.<\/td>\n<\/tr>\n<tr>\n<td>Erro de certificado<\/td>\n<td>Nome do servidor e cadeia de confian\u00e7a; n\u00e3o use <code>InsecureSkipVerify<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>Resposta inesperada<\/td>\n<td>Endpoint correto, namespace, vers\u00e3o e contrato WSDL.<\/td>\n<\/tr>\n<tr>\n<td>Prazo excedido<\/td>\n<td>Lat\u00eancia, n\u00famero de VMs e limites configurados.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>O programa utiliza dez segundos por requisi\u00e7\u00e3o, trinta para a coleta e cinco para a limpeza. S\u00e3o escolhas did\u00e1ticas, n\u00e3o valores universais. Ajuste-os com medi\u00e7\u00e3o. N\u00e3o acrescente repeti\u00e7\u00e3o autom\u00e1tica indiscriminada quando evoluir para a\u00e7\u00f5es de escrita: um timeout n\u00e3o prova que o servidor deixou de executar a opera\u00e7\u00e3o.<\/p>\n<p>Quer evoluir de consultas para a\u00e7\u00f5es sobre as VMs? Revise na <a href=\"\/2026\/09\/api-do-virtualbox-parte-1-soap-sessoes-e-seguranca\/\">parte 1<\/a> os cuidados com sess\u00f5es de m\u00e1quina, bloqueios, autoriza\u00e7\u00e3o e opera\u00e7\u00f5es de escrita.<\/p>\n<h2>Como transformar o exemplo em um projeto maior<\/h2>\n<ul>\n<li>Fixar uma matriz de vers\u00f5es e validar o WSDL correspondente.<\/li>\n<li>Separar transporte, autentica\u00e7\u00e3o, invent\u00e1rio e opera\u00e7\u00f5es de escrita.<\/li>\n<li>Adicionar testes com respostas reais sanitizadas, al\u00e9m dos simulados.<\/li>\n<li>Tratar pagina\u00e7\u00e3o ou limites da aplica\u00e7\u00e3o, cache e prazo total de coleta.<\/li>\n<li>Para agentes persistentes, estudar libera\u00e7\u00e3o de refer\u00eancias e reconex\u00e3o, sem acumular objetos indefinidamente.<\/li>\n<li>Versionar o contrato p\u00fablico sem expor os detalhes SOAP aos consumidores.<\/li>\n<\/ul>\n<p>Se o escopo crescer, um cliente gerado a partir do WSDL pode reduzir trabalho repetitivo, mas ainda exige valida\u00e7\u00e3o de tipos, namespaces e falhas. O pequeno cliente manual deste artigo serve para entender o caminho, n\u00e3o para reimplementar toda a API \u00e0 m\u00e3o.<\/p>\n<p>Esse trabalho se conecta \u00e0 nossa s\u00e9rie: <a href=\"\/2026\/09\/phpvirtualbox-em-go-parte-1-echo-vue-e-binario-unico\/\">arquitetura em Go, Echo e Vue<\/a>, <a href=\"\/2026\/09\/phpvirtualbox-em-go-parte-2-migracao-com-ia-e-openspec\/\">planejamento com IA e OpenSpec<\/a> e <a href=\"\/2026\/09\/phpvirtualbox-em-go-parte-3-roteiro-de-implementacao-e-testes\/\">implementa\u00e7\u00e3o incremental e testes<\/a>. Aqui, a base \u00e9 pr\u00e1tica: primeiro consultar corretamente; depois ampliar com evid\u00eancias.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Crea un cliente en Go para la API SOAP de VirtualBox: autenticaci\u00f3n, inventario de m\u00e1quinas virtuales, cierre de sesi\u00f3n, tiempos de espera, seguridad y pruebas.<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[45,2,120],"tags":[31],"class_list":["post-1616","post","type-post","status-publish","format-standard","hentry","category-desenv","category-linux","category-servidores","tag-golang"],"_links":{"self":[{"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/posts\/1616","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/comments?post=1616"}],"version-history":[{"count":3,"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/posts\/1616\/revisions"}],"predecessor-version":[{"id":1622,"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/posts\/1616\/revisions\/1622"}],"wp:attachment":[{"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/media?parent=1616"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/categories?post=1616"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.linuxpro.com.br\/es\/wp-json\/wp\/v2\/tags?post=1616"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}