I am using Revel framework for golang. I have a subdirectory in my controllers folder like below
controllers
new
app2.go
app1.go
Contents of app1.go
package controllers
import (
"github.com/revel/revel"
)
type APP1 struct {
*revel.Controller
}
func (c APP1) Show() revel.Result {
}
Contents of app2.go
import (
"github.com/revel/revel"
)
type APP2 struct {
*revel.Controller
}
func (c APP2) Show() revel.Result {
}
My routes file is like this
GET /v1/show APP1.show
GET /v2/show APP2.show
When I call /v2/show it gives me error failed to find controller APP2 while v1/show is working absolutely working fine. Can anybody tell me how to fix it.
Configure the route for APP2.show like this GET /new/v2/show
Add the below at the top of app2.go
package controllers
I tried the same using a similar example and it worked for me. I would suggest not to use full capitalized controller names; use App2 instead of APP2.
I was a little confused to find out that the methods referenced from the router need to be Pascal Cased to be recognized (e.g. like above func (c APP2) DescribeService() revel.Result). This might be obvious to seasoned Revel or Go developers but really wasn't obvious to me.
Related
I want to expose the following URLs from my service:
GET /api/foo
GET /api/bar
I also want to structure it as a router nested inside another. The toplevel router will match all requests to /api and serve them with the nested router which will match requests to /foo and /bar. Basically, namespacing.
I could just have a single router and give the /api prefix to both the routes:
router.GET("/api/foo", apiFoo)
router.GET("/api/bar", apiBar)
But I would like the convenience of having a single router for all routes inside the /api prefix so that I can add appropriate middlewares to them all with a single function call.
Here's what I tried:
package main
import (
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
func apiFoo(w http.ResponseWriter, r *http.Request) {}
func apiBar(w http.ResponseWriter, r *http.Request) {}
func main() {
api := httprouter.New()
api.HandlerFunc(http.MethodGet, "/foo", apiFoo)
api.HandlerFunc(http.MethodGet, "/bar", apiBar)
router := httprouter.New()
router.Handler(http.MethodGet, "/api", api)
log.Fatal(http.ListenAndServe(":8080", router))
}
However, getting 404 not found on going to http://localhost:8080/api/foo or http://localhost:8080/api/bar
I had thought that nested routers would work because routers implement the http.Handler interface. What am I missing?
httprouter does not concatenate the path, so you can't do it that way. Both routers will just inspect the request path and act accordingly.
There is an open pull request for 7 years, that would implement it. You could have a look there and implement similar logic yourself, the PR is based on concatenating the path. Maybe you can write a small helper function for that.
If you are willing to switch the router package, you could look into alternatives such as chi, which support router groups.
How would one go about adding a method to a struct that is a different file?
This is what I've tried so far but it doesn't seem to be working.
// ./src
package routes
type App struct{
}
func (a *App) initializeRoutes() {
a.Subrouter.HandleFunc("/products", a.getSomething).Methods("GET")
}
// ./src/controller
package routes
func (a *App) getSomething(w http.ResponseWriter, r *http.Request){
...
}
They are in the same package.
They are not in the same package. A Go package has both a name and a path. They're both named routes but they have different paths. The actual packages are routes and controller/routes. The result is subdirectories are different packages.
See Package Names on the Go Blog for more information.
Since they're in different packages, they can only access each other's public members and exported methods. You can't monkey patch someone else's package or interface in Go. This is by design to keep all the functionality of a package in one place, no action-at-a-distance.
You have options. You could put all methods of routes into a single package. If they all belong together, there's no need to split it up into multiple files.
If they don't really belong together, you can write a new struct with routes embedded into it and define new methods on that. Then you can access either the wrapper struct to get your added methods, or its embedded struct to get routes' methods. See this answer for an example.
But really I think you need to think about how your code is arranged. The App probably shouldn't be defined by the routes package, they should be separate. Instead, Go prefers a has-a relationship. App would contain an instance of routes.Route.
You'd rearrange your code tree like so:
app/
app.go
app_test.go
routes/
routes.go
routes_test.go
Note that rather than having it all in src/ it's now contained in its own project directory. app.go would look something like this.
// src/app/app.go
package app
import(
"app/routes"
"fmt"
"net/http"
)
type App struct {
routes routes.Route
}
func (a *App) initializeRoutes() {
a.routes.AddRoute("/products", a.getSomething)
}
func (a *App) getSomething(w http.ResponseWriter, r *http.Request) {
fmt.Println("Something!")
}
Notice how we're delegating responsibility to adding a route to a.routes rather than having App do it itself. This avoids the desire to smash all functionality into one ginormous package. routes.Route would be defined in app/routes/routes.go.
// src/app/routes/routes.go
package routes
import "net/http"
// A type specifying the interface for route handlers.
type RouteHandler func(w http.ResponseWriter, r *http.Request)
type Route struct {
handlers map[string]RouteHandler
}
func (r *Route) AddRoute(path string, handler RouteHandler) {
r.handlers[path] = handler
}
Now all routes has to worry about is handling routes. It doesn't know anything about your specific application logic.
I was trying to get my http.res & http.req functions in a controllers file.
Now that we've rearranged the file structure, you can do that. You can, if you like, define app/controllers.go to organize your code.
// src/app/controllers.go
package app
import(
"fmt"
"net/http"
)
func (a *App) getSomething(w http.ResponseWriter, r *http.Request) {
fmt.Println("Something!")
}
app/app.go and app/controllers.go are in the same package. They have the same path and the same name. So app/controllers.go can add methods to App.
You have an error because your files belong to different packages. Everything related to one struct must be in the same package.
It is possible to declare struct and its methods in different files, but they must belong same package (be in same folder).
I am using gin gonic to build a web application. I use https://github.com/gin-gonic/contrib/tree/master/sessions to handle session. Forexample, i set a integer value to session:
function Test(c *gin.Context){
session:= sessions.Default(c)
session.Set("mysession",123)
session.Save()
}
And in another controllers, i can get this session by session.Get("mysession").
But if i set map or struct. I only can get the session in the same controller. something wrong here??
You probably forgot to register it, when your app starts you need to have something like:
package main
import (
"encoding/gob"
"path/to/yourpackage"
func init() {
gob.Register(&yourpackage.YourStruct{})
}
You can look here http://www.gorillatoolkit.org/pkg/sessions for more information (gin-gonic uses gorilla sessions under the hood)
I need a way to load objects via IoC provider depending on a request parameter. Right now I'm loading my objects directly with App::make(xy, [$urlParamter] which I want to refactor, so that I can use dependency injection the way it is supposed to. To describe my current architecture I need to show you quiet some information and in the end you find my concrete questions I have about it.
I'm building up a general CMS framework which provides an import architecture that is extendable with a custom import implementation.
Now I'm struggling with properly loading the concrete classes via IoC container, because they always depend on the selected import.
To dig into my problem, here is my entry point in routes.php
Route::get('/import', ['as' => 'overview', 'uses' => '\CMSFramework\Http\Controllers\Import\ImportController#index']);
This generates a view where the user selects a concrete import to be triggered. After selecting a concrete import, the user should get individual views to prepare the appropriate import (i.e. Upload a CSV file, select an area to import, etc.)
In my concept an import implementation consist of:
A controller class, to implement specific (peraration-) tasks like uploading a CSV file. It inherits from a base controller of the cms framework
An import "business" or "service" class, that implements how the data is getting imported (and may further delegate to queued jobs etc.)
The CMS framework part consists of:
A base controller class for all common/shared import tasks like (start the prepared import, clean all working data, etc.)
A base service class ImportBase where all implementations inherit from. It provides an interface to receive a progress for any import and implements shared operations like cleaning up working data, etc.)
An ImportStatus class which is part of the ImportBase-Class via $ImportBase->status() to handle all runtime status informations (like "is the job still running, what is the progress). This class also provides a containter for a so called "payload" that allows any conrete import implementation to push and fetch custom status informations (ie. any sub-process has been finished)
So back to my IoC architecture. After the user selected a concrete import, the following route delegates the action to the custom import implementation's controller. If it's a framework supported standard-action like via URL /import/<importkey>/clean, the inherited BaseController of the cms framework takes over and handles the request
Route::get('/import/{key}/{method}', ['uses' => function($key, $method) {
return App::make('\\MadeleinePim\\Http\\Controllers\\Import\\'.ucfirst(camel_case($key)).'Controller')->$method($key);
}]);
I know that this direct binding via a naming convention can be improved (maybe via a custom configuration file), but for now this works for me.
Now I need to show an example of how I tried to implement a concrete import target in my controller via /import/<importkey>/seedCsvDataToDatabase:
public function seedCsvDataToDatabase($key)
{
// The IoC binding is shown in next code snippet. I did not found a good way to use method injection because
// of the route-specific parameters that control the responsible import implementation
$import = \App::make(Import::class, [$key]);
// Now trigger the import service operation of that concrete import implementation (probably bad design here)
$import->seed();
// Now, that this preparation task is done, I use the ImportStatus object which is part of the Import to store
// status informations. With this I can then decided in which step the user is (Think of it like a wizard to
// prepare any import)
$import->status()
->set(ConcreteImport::STATUS_SEEDED, true)
->set(ConcreteImport::STATUS_SEEDED_DURATION_SECONDS, (microtime(true) - $time_start) / 60);
// Back to controller method that determines in which status the import is to delegate/redirect to different
// views.
return redirect('/import/<importkey>');
}
My IoC binding for the Import class:
$this->app->singleton(Import::class, function ($app, array $parameters) {
$importKey = head($parameters);
// There is a config file that provides the class names of the concrete import implementations
$importClassName = config()->get('import.' . $importKey);
if (!$importClassName) {
throw new ImportNotFoundException($importKey, "Import with key '{$importKey}' is not setup properly'");
}
$importReflectionClass = new \ReflectionClass($importClassName);
return $importReflectionClass->newInstance($importKey);
});
And finally, the lazy loading of the import status, which is encapsulated in the ImportStatus object looks like this
public function status()
{
if (!$this->status) {
$this->status = \App::make(ImportStatus::class, [$this->key()]);
}
return $this->status;
}
I hope that demonstrates the way I try to resolve my import objects from the IoC container.
My learning so far is, that this is not the right way to inject my objects.
Is the assumption right, that I should not pass the $importKey at runtime to the App::make() and rather should try to make this independ?
My failed attempt on this was to make the IoC binding smarter and let it access the Request to properly inject my concrete import object with the required $importKey, like (pseudo code!):
$this->app->bind(ImportStatus::class, function(Container $app) {
// Did not find a good way to access the {key}-part of my route /import/{key}/{method}
$key = $app->make(Request::class)->get('key'); // Does not work like this
return new \Scoop\Import\ImportStatus($key);
});
Does this approach can work like this?
Can I somehow pass through the $importKey from my route to the ServiceProvider (or better pull it from there?)
Is there a better solution to initialize my concrete import implementations?
----------
UPDATE 1
For my lattest idea to access the Route in my IoC Binding, I got this way working:
$this->app->singleton(Import::class, function (Container $app) {
$importKey = \Route::current()->getParameter('key');
$importClassName = config()->get('import.' . $importKey);
$importReflectionClass = new \ReflectionClass($importClassName);
return $importReflectionClass->newInstance($importKey);
});
Nevertheless the idea of #Sandyandi N. dela Cruz to use a router binding prevents the direct coupling between the Binding and the Request which still doesn't feel right. Using router-binding to couple a request parameter to an implementation, sounds more appropriate.
I think you've dwelt to much on the IoC container there. Why not implement the Factory pattern and do a route binding instead of creating multiple controllers to handle different Imports? Crude example as follows:
Create a route binder - edit your app/Provider/RouteServiceProvider.php's boot() method
public function boot(Router $router)
{
parent::boot($router);
// Add the statement below.
$router->bind('import', 'App\RouteBindings#import');
}
Create the App\RouteBindings class as app/RouteBindings.php
Create an import() method with the following:
public function import($importKey, $route)
{
switch ($importKey) {
case 'import_abc':
return new ImportAbc;
break; // break; for good measure. ;)
case 'import_xyz':
return new ImportXyz;
break;
// and so on... you can add a `default` handler to throw an ImportNotFoundExeption.
}
}
Create a route for resolving an Import class.
Route::get('import/{import}/{method}', 'ImportController#handleImport');
Here, {import} will return the proper Import concrete class based on your URL.
In your ImportController's handleImport() you can do the following:
public function handleImport(Import $import, $method)
{
// $import is already a concrete class resolved in the route binding.
$import->$method();
}
So when you hit: http://example.com/import/import_abc/seed, the route binding will return a concrete class of ImportAbc and store it in $import on your handleImport() method, then your handleImport() method will execute: $import->seed();. Tip: you should probably move other controller logic such as $import->status()->set() into the Import class. Keep your controllers thin.
Just make sure your Import classes have the same signature.
It's kinda like Laravel's Route Model Binding except you create the logic for the bindings.
Again, this is just a crude example but I hope it helps.
I am trying to use $this->load->add_package_path to add a new sub-application to my CI app. I can see how to use this for views and such: just put
$this->load->add_package_path("/mypackage");
in the controller ctor. Unfortunately, that doesn't help, because I want to find controllers from the package path: it seems like a chicken-and-egg problem. Is there somewhere else I can put a add_package_path call (such as index.php)?
Figured it out by tracing through CI. Turns out you can't do this. You can put your own controllers in a subdirectory of /application/controllers, but you cannot put them someplace else: in particular, as http://codeigniter.com/user_guide/libraries/loader.html implies,
These elements can be libraries (classes) View files, Helpers, Models, or your own files.
"Your own files" not including controllers. I ended up being able to get it to look to the ENVIRONMENT setting in addition to APPPATH by making changes to CodeIgniter.php and Router.php. I don't like making this kind of change, however, so I backed out the changes. I'll put this here in case somebody else can benefit from it.
Router.php change:
function _validate_request($segments)
{
...
// Does the requested controller exist in the root folder?
if (file_exists(APPPATH.'controllers/'.$segments[0].'.php')
|| file_exists(ENVIRONMENT.'/controllers/'.$segments[0].'.php')) // New bit here
{
return $segments;
}
....
}
CodeIgniter.php change, 'round about line 246:
if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'))
{
// New bit here
if ( ! file_exists(ENVIRONMENT.'/controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php'))
{
show_error('Unable to load your default controller. Please make sure the controller specified in your Routes.php file is valid.');
}
include(ENVIRONMENT.'/controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php');
} else {
include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->fetch_class().'.php');
}